using System; using System.Runtime.CompilerServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; namespace Unity.Netcode { /// /// Optimized class used for reading values from a byte stream /// /// /// /// public struct FastBufferReader : IDisposable { internal struct ReaderHandle { internal unsafe byte* BufferPointer; internal int Position; internal int Length; internal Allocator Allocator; #if DEVELOPMENT_BUILD || UNITY_EDITOR internal int AllowedReadMark; internal bool InBitwiseContext; #endif } internal unsafe ReaderHandle* Handle; /// /// Get the current read position /// public unsafe int Position { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Handle->Position; } /// /// Get the total length of the buffer /// public unsafe int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Handle->Length; } /// /// Gets a value indicating whether the reader has been initialized and a handle allocated. /// public unsafe bool IsInitialized => Handle != null; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void CommitBitwiseReads(int amount) { Handle->Position += amount; #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->InBitwiseContext = false; #endif } private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator copyAllocator, Allocator internalAllocator) { ReaderHandle* readerHandle = null; if (copyAllocator == Allocator.None) { readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle), UnsafeUtility.AlignOf(), internalAllocator); readerHandle->BufferPointer = buffer; readerHandle->Position = offset; } else { readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf(), copyAllocator); UnsafeUtility.MemCpy(readerHandle + 1, buffer + offset, length); readerHandle->BufferPointer = (byte*)(readerHandle + 1); readerHandle->Position = 0; } readerHandle->Length = length; // If the copyAllocator provided is Allocator.None, there is a chance that the internalAllocator was provided // When we dispose, we are really only interested in disposing Allocator.Persistent and Allocator.TempJob // as disposing Allocator.Temp and Allocator.None would do nothing. Therefore, make sure we dispose the readerHandle with the right Allocator label readerHandle->Allocator = copyAllocator == Allocator.None ? internalAllocator : copyAllocator; #if DEVELOPMENT_BUILD || UNITY_EDITOR readerHandle->AllowedReadMark = 0; readerHandle->InBitwiseContext = false; #endif return readerHandle; } /// /// Create a FastBufferReader from a NativeArray. /// /// A new buffer will be created using the given and the value will be copied in. /// FastBufferReader will then own the data. /// /// The exception to this is when the passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor /// stored anywhere in heap memory). This is true, unless the param is explicitly set /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// should manually call Dispose() when it is no longer needed. /// /// /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance /// /// /// The allocator type used for internal data when this reader points directly at a buffer owned by someone else public unsafe FastBufferReader(NativeArray buffer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp) { Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, copyAllocator, internalAllocator); } /// /// Create a FastBufferReader from an ArraySegment. /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// /// Allocator.None is not supported for byte[]. If you need this functionality, use a fixed() block /// and ensure the FastBufferReader isn't used outside that block. /// /// The buffer to copy from /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance /// The number of bytes to copy (all if this is -1) /// The offset of the buffer to start copying from public unsafe FastBufferReader(ArraySegment buffer, Allocator copyAllocator, int length = -1, int offset = 0) { if (copyAllocator == Allocator.None) { throw new NotSupportedException("Allocator.None cannot be used with managed source buffers."); } fixed (byte* data = buffer.Array) { Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, copyAllocator, Allocator.Temp); } } /// /// Create a FastBufferReader from an existing byte array. /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// /// Allocator.None is not supported for byte[]. If you need this functionality, use a fixed() block /// and ensure the FastBufferReader isn't used outside that block. /// /// The buffer to copy from /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance /// The number of bytes to copy (all if this is -1) /// The offset of the buffer to start copying from public unsafe FastBufferReader(byte[] buffer, Allocator copyAllocator, int length = -1, int offset = 0) { if (copyAllocator == Allocator.None) { throw new NotSupportedException("Allocator.None cannot be used with managed source buffers."); } fixed (byte* data = buffer) { Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, copyAllocator, Allocator.Temp); } } /// /// Create a FastBufferReader from an existing byte buffer. /// /// A new buffer will be created using the given and the value will be copied in. /// FastBufferReader will then own the data. /// /// The exception to this is when the passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor /// stored anywhere in heap memory). This is true, unless the param is explicitly set /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// should manually call Dispose() when it is no longer needed. /// /// The buffer to copy from /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance /// The number of bytes to copy /// The offset of the buffer to start copying from /// The allocator type used for internal data when this reader points directly at a buffer owned by someone else public unsafe FastBufferReader(byte* buffer, Allocator copyAllocator, int length, int offset = 0, Allocator internalAllocator = Allocator.Temp) { Handle = CreateHandle(buffer, length, offset, copyAllocator, internalAllocator); } /// /// Create a FastBufferReader from a FastBufferWriter. /// /// A new buffer will be created using the given and the value will be copied in. /// FastBufferReader will then own the data. /// /// The exception to this is when the passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor /// stored anywhere in heap memory). This is true, unless the param is explicitly set /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// should manually call Dispose() when it is no longer needed. /// /// The writer to copy from /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance /// The number of bytes to copy (all if this is -1) /// The offset of the buffer to start copying from /// The allocator type used for internal data when this reader points directly at a buffer owned by someone else public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp) { Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, copyAllocator, internalAllocator); } /// /// Create a FastBufferReader from another existing FastBufferReader. This is typically used when you /// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to /// a Persistent one to be processed later. /// /// A new buffer will be created using the given and the value will be copied in. /// FastBufferReader will then own the data. /// /// The exception to this is when the passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor /// stored anywhere in heap memory). /// /// The reader to copy from /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance /// The number of bytes to copy (all if this is -1) /// The offset of the buffer to start copying from /// The allocator type used for internal data when this reader points directly at a buffer owned by someone else public unsafe FastBufferReader(FastBufferReader reader, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp) { Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, copyAllocator, internalAllocator); } /// /// implementation that frees the allocated buffer /// public unsafe void Dispose() { UnsafeUtility.Free(Handle, Handle->Allocator); Handle = null; } /// /// Move the read position in the stream /// /// Absolute value to move the position to, truncated to Length [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Seek(int where) { Handle->Position = Math.Min(Length, where); } /// /// Mark that some bytes are going to be read via GetUnsafePtr(). /// /// Amount that will be read /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void MarkBytesRead(int amount) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } if (Handle->Position + amount > Handle->AllowedReadMark) { throw new OverflowException("Attempted to read without first calling TryBeginRead()"); } #endif Handle->Position += amount; } /// /// Retrieve a BitReader to be able to perform bitwise operations on the buffer. /// No bytewise operations can be performed on the buffer until bitReader.Dispose() has been called. /// At the end of the operation, FastBufferReader will remain byte-aligned. /// /// A BitReader public unsafe BitReader EnterBitwiseContext() { #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->InBitwiseContext = true; #endif return new BitReader(this); } /// /// Allows faster serialization by batching bounds checking. /// When you know you will be reading multiple fields back-to-back and you know the total size, /// you can call TryBeginRead() once on the total size, and then follow it with calls to /// ReadValue() instead of ReadValueSafe() for faster serialization. /// /// Unsafe read operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following /// operations in release builds. /// /// Amount of bytes to read /// True if the read is allowed, false otherwise /// If called while in a bitwise context [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginRead(int bytes) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } #endif if (Handle->Position + bytes > Handle->Length) { return false; } #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->AllowedReadMark = Handle->Position + bytes; #endif return true; } /// /// Allows faster serialization by batching bounds checking. /// When you know you will be reading multiple fields back-to-back and you know the total size, /// you can call TryBeginRead() once on the total size, and then follow it with calls to /// ReadValue() instead of ReadValueSafe() for faster serialization. /// /// Unsafe read operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following /// operations in release builds. /// /// the type `T` of the value you are trying to read /// The value you want to read /// True if the read is allowed, false otherwise /// If called while in a bitwise context [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginReadValue(in T value) where T : unmanaged { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } #endif int len = sizeof(T); if (Handle->Position + len > Handle->Length) { return false; } #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->AllowedReadMark = Handle->Position + len; #endif return true; } /// /// Internal version of TryBeginRead. /// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward. /// /// /// true upon success /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe bool TryBeginReadInternal(int bytes) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } #endif if (Handle->Position + bytes > Handle->Length) { return false; } #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->Position + bytes > Handle->AllowedReadMark) { Handle->AllowedReadMark = Handle->Position + bytes; } #endif return true; } /// /// Returns an array representation of the underlying byte buffer. /// !!Allocates a new array!! /// /// byte array [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte[] ToArray() { byte[] ret = new byte[Length]; fixed (byte* b = ret) { UnsafeUtility.MemCpy(b, Handle->BufferPointer, Length); } return ret; } /// /// Gets a direct pointer to the underlying buffer /// /// pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte* GetUnsafePtr() { return Handle->BufferPointer; } /// /// Gets a direct pointer to the underlying buffer at the current read position /// /// pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte* GetUnsafePtrAtCurrentPosition() { return Handle->BufferPointer + Handle->Position; } /// /// Read an INetworkSerializable /// /// /// INetworkSerializable instance /// public void ReadNetworkSerializable(out T value) where T : INetworkSerializable, new() { value = new T(); var bufferSerializer = new BufferSerializer(new BufferSerializerReader(this)); value.NetworkSerialize(bufferSerializer); } /// /// Read an array of INetworkSerializables /// /// INetworkSerializable instance /// the array to read the values of type `T` into /// public void ReadNetworkSerializable(out T[] value) where T : INetworkSerializable, new() { ReadValueSafe(out int size); value = new T[size]; for (var i = 0; i < size; ++i) { ReadNetworkSerializable(out value[i]); } } /// /// Read an INetworkSerializable in-place, without constructing a new one /// Note that this will NOT check for null before calling NetworkSerialize /// /// /// INetworkSerializable instance /// public void ReadNetworkSerializableInPlace(ref T value) where T : INetworkSerializable { var bufferSerializer = new BufferSerializer(new BufferSerializerReader(this)); value.NetworkSerialize(bufferSerializer); } /// /// Reads a string /// NOTE: ALLOCATES /// /// Stores the read string /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void ReadValue(out string s, bool oneByteChars = false) { ReadValue(out uint length); s = "".PadRight((int)length); int target = s.Length; fixed (char* native = s) { if (oneByteChars) { for (int i = 0; i < target; ++i) { ReadByte(out byte b); native[i] = (char)b; } } else { ReadBytes((byte*)native, target * sizeof(char)); } } } /// /// Reads a string. /// NOTE: ALLOCATES /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// Stores the read string /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } #endif if (!TryBeginReadInternal(sizeof(uint))) { throw new OverflowException("Reading past the end of the buffer"); } ReadValue(out uint length); if (!TryBeginReadInternal((int)length * (oneByteChars ? 1 : sizeof(char)))) { throw new OverflowException("Reading past the end of the buffer"); } s = "".PadRight((int)length); int target = s.Length; fixed (char* native = s) { if (oneByteChars) { for (int i = 0; i < target; ++i) { ReadByte(out byte b); native[i] = (char)b; } } else { ReadBytes((byte*)native, target * sizeof(char)); } } } /// /// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it. /// /// Value to read /// Number of bytes /// Offset into the value to write the bytes /// the type value to read the value into /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } if (Handle->Position + bytesToRead > Handle->AllowedReadMark) { throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()"); } #endif var val = new T(); byte* ptr = ((byte*)&val) + offsetBytes; byte* bufferPointer = Handle->BufferPointer + Handle->Position; UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); Handle->Position += bytesToRead; value = val; } /// /// Read a byte to the stream. /// /// Stores the read value [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadByte(out byte value) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } if (Handle->Position + 1 > Handle->AllowedReadMark) { throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()"); } #endif value = Handle->BufferPointer[Handle->Position++]; } /// /// Read a byte to the stream. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// Stores the read value [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadByteSafe(out byte value) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } #endif if (!TryBeginReadInternal(1)) { throw new OverflowException("Reading past the end of the buffer"); } value = Handle->BufferPointer[Handle->Position++]; } /// /// Read multiple bytes to the stream /// /// Pointer to the destination buffer /// Number of bytes to read - MUST BE <= BUFFER SIZE /// Offset of the byte buffer to store into [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBytes(byte* value, int size, int offset = 0) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } if (Handle->Position + size > Handle->AllowedReadMark) { throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()"); } #endif UnsafeUtility.MemCpy(value + offset, (Handle->BufferPointer + Handle->Position), size); Handle->Position += size; } /// /// Read multiple bytes to the stream /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// Pointer to the destination buffer /// Number of bytes to read - MUST BE <= BUFFER SIZE /// Offset of the byte buffer to store into [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBytesSafe(byte* value, int size, int offset = 0) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferReader in bytewise mode while in a bitwise context."); } #endif if (!TryBeginReadInternal(size)) { throw new OverflowException("Reading past the end of the buffer"); } UnsafeUtility.MemCpy(value + offset, (Handle->BufferPointer + Handle->Position), size); Handle->Position += size; } /// /// Read multiple bytes from the stream /// /// Pointer to the destination buffer /// Number of bytes to read - MUST BE <= BUFFER SIZE /// Offset of the byte buffer to store into [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBytes(ref byte[] value, int size, int offset = 0) { fixed (byte* ptr = value) { ReadBytes(ptr, size, offset); } } /// /// Read multiple bytes from the stream /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// Pointer to the destination buffer /// Number of bytes to read - MUST BE <= BUFFER SIZE /// Offset of the byte buffer to store into [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBytesSafe(ref byte[] value, int size, int offset = 0) { fixed (byte* ptr = value) { ReadBytesSafe(ptr, size, offset); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanaged(out T value) where T : unmanaged { fixed (T* ptr = &value) { byte* bytes = (byte*)ptr; ReadBytes(bytes, sizeof(T)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanagedSafe(out T value) where T : unmanaged { fixed (T* ptr = &value) { byte* bytes = (byte*)ptr; ReadBytesSafe(bytes, sizeof(T)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanaged(out T[] value) where T : unmanaged { ReadUnmanaged(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value = new T[sizeInTs]; fixed (T* ptr = value) { byte* bytes = (byte*)ptr; ReadBytes(bytes, sizeInBytes); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged { ReadUnmanagedSafe(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value = new T[sizeInTs]; fixed (T* ptr = value) { byte* bytes = (byte*)ptr; ReadBytesSafe(bytes, sizeInBytes); } } /// /// Read a NetworkSerializable value /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); /// /// Read a NetworkSerializable array /// /// The type being serialized /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); /// /// Read a NetworkSerializable value /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); /// /// Read a NetworkSerializable array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); /// /// Read a struct /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value); /// /// Read a struct array /// /// The type being serialized /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value); /// /// Read a struct /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value); /// /// Read a struct array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value); /// /// Read a primitive value (int, bool, etc) /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly /// on values that are not primitives. /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => ReadUnmanaged(out value); /// /// Read a primitive value array (int, bool, etc) /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly /// on values that are not primitives. /// /// The type being serialized /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => ReadUnmanaged(out value); /// /// Read a primitive value (int, bool, etc) /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly /// on values that are not primitives. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => ReadUnmanagedSafe(out value); /// /// Read a primitive value (int, bool, etc) /// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly /// on values that are not primitives. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => ReadUnmanagedSafe(out value); /// /// Read an enum value /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value); /// /// Read an enum array /// /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value); /// /// Read an enum value /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The value to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value); /// /// Read an enum array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// The type being serialized /// The values to read /// An unused parameter used for enabling overload resolution based on generic constraints [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value); /// /// Read a Vector2 /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector2 value) => ReadUnmanaged(out value); /// /// Read a Vector2 array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector2[] value) => ReadUnmanaged(out value); /// /// Read a Vector3 /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector3 value) => ReadUnmanaged(out value); /// /// Read a Vector3 array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value); /// /// Read a Vector2Int /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector2Int value) => ReadUnmanaged(out value); /// /// Read a Vector2Int array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector2Int[] value) => ReadUnmanaged(out value); /// /// Read a Vector3Int /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector3Int value) => ReadUnmanaged(out value); /// /// Read a Vector3Int array /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector3Int[] value) => ReadUnmanaged(out value); /// /// Read a Vector4 /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector4 value) => ReadUnmanaged(out value); /// /// Read a Vector4 /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value); /// /// Read a Quaternion /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Quaternion value) => ReadUnmanaged(out value); /// /// Read a Quaternion array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Quaternion[] value) => ReadUnmanaged(out value); /// /// Read a Color /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Color value) => ReadUnmanaged(out value); /// /// Read a Color array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Color[] value) => ReadUnmanaged(out value); /// /// Read a Color32 /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Color32 value) => ReadUnmanaged(out value); /// /// Read a Color32 array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Color32[] value) => ReadUnmanaged(out value); /// /// Read a Ray /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Ray value) => ReadUnmanaged(out value); /// /// Read a Ray array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Ray[] value) => ReadUnmanaged(out value); /// /// Read a Ray2D /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Ray2D value) => ReadUnmanaged(out value); /// /// Read a Ray2D array /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValue(out Ray2D[] value) => ReadUnmanaged(out value); /// /// Read a Vector2 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector2 value) => ReadUnmanagedSafe(out value); /// /// Read a Vector2 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector2[] value) => ReadUnmanagedSafe(out value); /// /// Read a Vector3 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector3 value) => ReadUnmanagedSafe(out value); /// /// Read a Vector3 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value); /// /// Read a Vector2Int /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector2Int value) => ReadUnmanagedSafe(out value); /// /// Read a Vector2Int array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector2Int[] value) => ReadUnmanagedSafe(out value); /// /// Read a Vector3Int /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector3Int value) => ReadUnmanagedSafe(out value); /// /// Read a Vector3Int array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector3Int[] value) => ReadUnmanagedSafe(out value); /// /// Read a Vector4 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value); /// /// Read a Vector4 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value); /// /// Read a Quaternion /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Quaternion value) => ReadUnmanagedSafe(out value); /// /// Read a Quaternion array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Quaternion[] value) => ReadUnmanagedSafe(out value); /// /// Read a Color /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Color value) => ReadUnmanagedSafe(out value); /// /// Read a Collor array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Color[] value) => ReadUnmanagedSafe(out value); /// /// Read a Color32 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Color32 value) => ReadUnmanagedSafe(out value); /// /// Read a Color32 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Color32[] value) => ReadUnmanagedSafe(out value); /// /// Read a Ray /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Ray value) => ReadUnmanagedSafe(out value); /// /// Read a Ray array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Ray[] value) => ReadUnmanagedSafe(out value); /// /// Read a Ray2D /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value); /// /// Read a Ray2D array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the values to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value); // There are many FixedString types, but all of them share the interfaces INativeList and IUTF8Bytes. // INativeList provides the Length property // IUTF8Bytes provides GetUnsafePtr() // Those two are necessary to serialize FixedStrings efficiently // - otherwise we'd just be memcpying the whole thing even if // most of it isn't used. /// /// Read a FixedString value. /// This method is a little difficult to use, since you have to know the size of the string before /// reading it, but is useful when the string is a known, fixed size. Note that the size of the /// string is also encoded, so the size to call TryBeginRead on is actually the fixed size (in bytes) /// plus sizeof(int) /// /// the value to read /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadValue(out T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { ReadUnmanaged(out int length); value = new T(); value.Length = length; ReadBytes(value.GetUnsafePtr(), length); } /// /// Read a FixedString value. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadValueSafe(out T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { ReadUnmanagedSafe(out int length); value = new T(); value.Length = length; ReadBytesSafe(value.GetUnsafePtr(), length); } /// /// Read a FixedString value. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple reads at once by calling TryBeginRead. /// /// the value to read /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadValueSafeInPlace(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { ReadUnmanagedSafe(out int length); value.Length = length; ReadBytesSafe(value.GetUnsafePtr(), length); } } }