This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs
Unity Technologies add668dfd2 com.unity.netcode.gameobjects@1.0.0-pre.8
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.8] - 2022-04-27

### Changed

- `unmanaged` structs are no longer universally accepted as RPC parameters because some structs (i.e., structs with pointers in them, such as `NativeList<T>`) can't be supported by the default memcpy struct serializer. Structs that are intended to be serialized across the network must add `INetworkSerializeByMemcpy` to the interface list (i.e., `struct Foo : INetworkSerializeByMemcpy`). This interface is empty and just serves to mark the struct as compatible with memcpy serialization. For external structs you can't edit, you can pass them to RPCs by wrapping them in `ForceNetworkSerializeByMemcpy<T>`. (#1901)

### Removed
- Removed `SIPTransport` (#1870)

- Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs).

### Fixed

- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890)
- Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
- Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883)
- Fixed an issue where UNetTransport.StartServer would return success even if the underlying transport failed to start (#854)
- Passing generic types to RPCs no longer causes a native crash (#1901)
- Fixed an issue where calling `Shutdown` on a `NetworkManager` that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877)
2022-04-27 00:00:00 +00:00

170 lines
8.2 KiB
C#

using System;
namespace Unity.Netcode
{
/// <summary>
/// Support methods for reading/writing NetworkVariables
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
/// but there's no way to achieve the same thing with a class, this includes various read/write delegates
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
/// which is invoked by code generated by ILPP during module load.
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
///
/// Any type that inherits from `NetworkVariableSerialization<T>` will implicitly result in any `T`
/// passed to it being picked up and initialized by ILPP.
///
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
/// </summary>
[Serializable]
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize structs
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
writer.WriteValueSafe(value);
}
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
reader.ReadValueSafe(out value);
}
// Functions that serialize enums
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, Enum
{
writer.WriteValueSafe(value);
}
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, Enum
{
reader.ReadValueSafe(out value);
}
// Functions that serialize other types
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
writer.WriteValueSafe(value);
}
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
reader.ReadValueSafe(out value);
}
// Should never be reachable at runtime. All calls to this should be replaced with the correct
// call above by ILPP.
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged
{
if (value is INetworkSerializable)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is INetworkSerializeByMemcpy)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is Enum)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization<TForMethod>.Write(writer, value);
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization<TForMethod>.Read(reader, out value);
}
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
protected internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
// These static delegates provide the right implementation for writing and reading a particular network variable type.
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
//
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
//
// In the future we may be able to use this to provide packing implementations for floats and integers to optimize bandwidth usage.
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
private static WriteDelegate<T> s_Write = WriteValue;
private static ReadDelegate<T> s_Read = ReadValue;
protected static void Write(FastBufferWriter writer, in T value)
{
s_Write(writer, value);
}
protected static void Read(FastBufferReader reader, out T value)
{
s_Read(reader, out value);
}
internal static void SetWriteDelegate(WriteDelegate<T> write)
{
s_Write = write;
}
internal static void SetReadDelegate(ReadDelegate<T> read)
{
s_Read = read;
}
protected NetworkVariableSerialization(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
}
}
}