Description
Description
The Duplicate
method in UnmanagedNetworkSerializableSerializer
causes errors when handling types that implement IDisposable
due to pure assignment. This leads to issues such as multiple Dispose
calls on NativeArray
, resulting in runtime errors.
Reproduce Steps
- Create a struct that implements
IDisposable
andINetworkSerializable
. - Use this struct as a type for a
NetworkVariable
. - Perform operations that involve serialization and deserialization of this struct.
- Observe the errors related to multiple
Dispose
calls.
Actual Outcome
When using a struct that implements IDisposable
with NetworkVariable
, the Duplicate
method causes multiple Dispose
calls on NativeArray
, leading to runtime errors and instability.
Expected Outcome
The Duplicate
method should handle types that implement IDisposable
correctly, ensuring that Dispose
is called only once, avoiding multiple Dispose
calls and ensuring stable runtime behavior.
Environment
- OS: [e.g. macOS Monterey]
- Unity Version: [e.g. 2023.1.20]
- Netcode Version: [e.g. 1.9.1]
Additional Context
Example of the problematic class:
public struct NativeArray2D<T> : IDisposable, INetworkSerializable
where T : unmanaged
{
private int _width;
private int _height;
private NativeArray<T> _array;
public NativeArray<T>.ReadOnly RawArray => _array.AsReadOnly();
public readonly int Width => _width;
public readonly int Height => _height;
public int Length => _array.Length;
public NativeArray2D(int width, int height, Allocator allocator)
{
_array = new NativeArray<T>(width * height, allocator);
_width = width;
_height = height;
}
public NativeArray2D(int width, int height, Allocator allocator, T defaultValue) : this(width, height, allocator)
{
for (var i = 0; i < _array.Length; i++)
{
_array[i] = defaultValue;
}
}
public T this[int x, int y]
{
get => _array[x + y * Width];
set => _array[x + y * Width] = value;
}
public void Dispose()
{
_array.Dispose();
_array = default;
}
public void NetworkSerialize<T1>(BufferSerializer<T1> serializer) where T1 : IReaderWriter
{
serializer.SerializeValue(ref _width);
serializer.SerializeValue(ref _height);
if (serializer.IsWriter)
{
if (_array.IsCreated)
{
serializer.GetFastBufferWriter().WriteValueSafe(true);
serializer.SerializeValue(ref _array, Allocator.Persistent);
}
else
{
serializer.GetFastBufferWriter().WriteValueSafe(false);
}
}
else
{
serializer.GetFastBufferReader().ReadValueSafe(out bool isCreated);
if (isCreated)
{
serializer.SerializeValue(ref _array, Allocator.Persistent);
}
}
}
}
Solution
The current implementation of the Duplicate
method in UnmanagedNetworkSerializableSerializer
does not handle disposable types correctly because it uses pure assignment. To address this issue, consider using UserNetworkVariableSerialization
's Duplicate
method if available, or implement a custom serialization mechanism similar to the managed approach. For example:
if (UserNetworkVariableSerialization.Duplicate != null)
{
return UserNetworkVariableSerialization.Duplicate(value);
}
else
{
using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue);
var refValue = value;
Write(writer, ref refValue);
using var reader = new FastBufferReader(writer, Allocator.None);
Read(reader, ref duplicatedValue);
return duplicatedValue;
}
Disposable structs, even if unmanaged, should be handled separately to ensure proper resource management. This prevents issues related to multiple Dispose calls and ensures stable operation of the networked application.