using System; using System.Runtime.CompilerServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; namespace Unity.Netcode { /// /// Optimized class used for writing values into a byte stream /// /// /// /// public struct FastBufferWriter : IDisposable { internal struct WriterHandle { internal unsafe byte* BufferPointer; internal int Position; internal int Length; internal int Capacity; internal int MaxCapacity; internal Allocator Allocator; internal bool BufferGrew; #if DEVELOPMENT_BUILD || UNITY_EDITOR internal int AllowedWriteMark; internal bool InBitwiseContext; #endif } internal unsafe WriterHandle* Handle; private static byte[] s_ByteArrayCache = new byte[65535]; /// /// The current write position /// public unsafe int Position { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Handle->Position; } /// /// The current total buffer size /// public unsafe int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Handle->Capacity; } /// /// The maximum possible total buffer size /// public unsafe int MaxCapacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Handle->MaxCapacity; } /// /// The total amount of bytes that have been written to the stream /// public unsafe int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Handle->Position > Handle->Length ? Handle->Position : Handle->Length; } /// /// Gets a value indicating whether the writer has been initialized and a handle allocated. /// public unsafe bool IsInitialized => Handle != null; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void CommitBitwiseWrites(int amount) { Handle->Position += amount; #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->InBitwiseContext = false; #endif } /// /// Create a FastBufferWriter. /// /// Size of the buffer to create /// Allocator to use in creating it /// Maximum size the buffer can grow to. If less than size, buffer cannot grow. public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1) { // Allocating both the Handle struct and the buffer in a single allocation - sizeof(WriterHandle) + size // The buffer for the initial allocation is the next block of memory after the handle itself. // If the buffer grows, a new buffer will be allocated and the handle pointer pointed at the new location... // The original buffer won't be deallocated until the writer is destroyed since it's part of the handle allocation. Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf(), allocator); #if DEVELOPMENT_BUILD || UNITY_EDITOR UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size); #endif Handle->BufferPointer = (byte*)(Handle + 1); Handle->Position = 0; Handle->Length = 0; Handle->Capacity = size; Handle->Allocator = allocator; Handle->MaxCapacity = maxSize < size ? size : maxSize; Handle->BufferGrew = false; #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->AllowedWriteMark = 0; Handle->InBitwiseContext = false; #endif } /// /// implementation that frees the allocated buffer /// public unsafe void Dispose() { if (Handle->BufferGrew) { UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator); } UnsafeUtility.Free(Handle, Handle->Allocator); Handle = null; } /// /// Move the write position in the stream. /// Note that moving forward past the current length will extend the buffer's Length value even if you don't write. /// /// Absolute value to move the position to, truncated to Capacity [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Seek(int where) { // This avoids us having to synchronize length all the time. // Writing things is a much more common operation than seeking // or querying length. The length here is a high watermark of // what's been written. So before we seek, if the current position // is greater than the length, we update that watermark. // When querying length later, we'll return whichever of the two // values is greater, thus if we write past length, length increases // because position increases, and if we seek backward, length remembers // the position it was in. // Seeking forward will not update the length. where = Math.Min(where, Handle->Capacity); if (Handle->Position > Handle->Length && where < Handle->Position) { Handle->Length = Handle->Position; } Handle->Position = where; } /// /// Truncate the stream by setting Length to the specified value. /// If Position is greater than the specified value, it will be moved as well. /// /// The value to truncate to. If -1, the current position will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void Truncate(int where = -1) { if (where == -1) { where = Position; } if (Handle->Position > where) { Handle->Position = where; } if (Handle->Length > where) { Handle->Length = where; } } /// /// Retrieve a BitWriter to be able to perform bitwise operations on the buffer. /// No bytewise operations can be performed on the buffer until bitWriter.Dispose() has been called. /// At the end of the operation, FastBufferWriter will remain byte-aligned. /// /// A BitWriter public unsafe BitWriter EnterBitwiseContext() { #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->InBitwiseContext = true; #endif return new BitWriter(this); } internal unsafe void Grow(int additionalSizeRequired) { var desiredSize = Handle->Capacity * 2; while (desiredSize < Position + additionalSizeRequired) { desiredSize *= 2; } var newSize = Math.Min(desiredSize, Handle->MaxCapacity); byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf(), Handle->Allocator); #if DEVELOPMENT_BUILD || UNITY_EDITOR UnsafeUtility.MemSet(newBuffer, 0, newSize); #endif UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length); if (Handle->BufferGrew) { UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator); } Handle->BufferGrew = true; Handle->BufferPointer = newBuffer; Handle->Capacity = newSize; } /// /// Allows faster serialization by batching bounds checking. /// When you know you will be writing multiple fields back-to-back and you know the total size, /// you can call TryBeginWrite() once on the total size, and then follow it with calls to /// WriteValue() instead of WriteValueSafe() for faster serialization. /// /// Unsafe write operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following /// operations in release builds. /// /// Amount of bytes to write /// True if the write is allowed, false otherwise /// If called while in a bitwise context [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWrite(int bytes) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } #endif if (Handle->Position + bytes > Handle->Capacity) { if (Handle->Position + bytes > Handle->MaxCapacity) { return false; } if (Handle->Capacity < Handle->MaxCapacity) { Grow(bytes); } else { return false; } } #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->AllowedWriteMark = Handle->Position + bytes; #endif return true; } /// /// Allows faster serialization by batching bounds checking. /// When you know you will be writing multiple fields back-to-back and you know the total size, /// you can call TryBeginWrite() once on the total size, and then follow it with calls to /// WriteValue() instead of WriteValueSafe() for faster serialization. /// /// Unsafe write operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following /// operations in release builds. Instead, attempting to write past the marked position in release builds /// will write to random memory and cause undefined behavior, likely including instability and crashes. /// /// The value type to write /// The value of the type `T` you want to write /// True if the write is allowed, false otherwise /// If called while in a bitwise context [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } #endif int len = sizeof(T); if (Handle->Position + len > Handle->Capacity) { if (Handle->Position + len > Handle->MaxCapacity) { return false; } if (Handle->Capacity < Handle->MaxCapacity) { Grow(len); } else { return false; } } #if DEVELOPMENT_BUILD || UNITY_EDITOR Handle->AllowedWriteMark = Handle->Position + len; #endif return true; } /// /// Internal version of TryBeginWrite. /// Differs from TryBeginWrite only in that it won't ever move the AllowedWriteMark backward. /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWriteInternal(int bytes) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } #endif if (Handle->Position + bytes > Handle->Capacity) { if (Handle->Position + bytes > Handle->MaxCapacity) { return false; } if (Handle->Capacity < Handle->MaxCapacity) { Grow(bytes); } else { return false; } } #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->Position + bytes > Handle->AllowedWriteMark) { Handle->AllowedWriteMark = Handle->Position + bytes; } #endif return true; } /// /// Returns an array representation of the underlying byte buffer. /// !!Allocates a new 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; } /// /// Uses a static cached array to create an array segment with no allocations. /// This array can only be used until the next time ToTempByteArray() is called on ANY FastBufferWriter, /// as the cached buffer is shared by all of them and will be overwritten. /// As such, this should be used with care. /// /// internal unsafe ArraySegment ToTempByteArray() { var length = Length; if (length > s_ByteArrayCache.Length) { return new ArraySegment(ToArray(), 0, length); } fixed (byte* b = s_ByteArrayCache) { UnsafeUtility.MemCpy(b, Handle->BufferPointer, length); } return new ArraySegment(s_ByteArrayCache, 0, length); } /// /// Gets a direct pointer to the underlying buffer /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte* GetUnsafePtr() { return Handle->BufferPointer; } /// /// Gets a direct pointer to the underlying buffer at the current read position /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte* GetUnsafePtrAtCurrentPosition() { return Handle->BufferPointer + Handle->Position; } /// /// Get the required size to write a string /// /// The string to write /// Whether or not to use one byte per character. This will only allow ASCII /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetWriteSize(string s, bool oneByteChars = false) { return sizeof(int) + s.Length * (oneByteChars ? sizeof(byte) : sizeof(char)); } /// /// Write an INetworkSerializable /// /// The value to write /// public void WriteNetworkSerializable(in T value) where T : INetworkSerializable { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(this)); value.NetworkSerialize(bufferSerializer); } /// /// Write an array of INetworkSerializables /// /// The value to write /// /// /// public void WriteNetworkSerializable(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; WriteValueSafe(sizeInTs); foreach (var item in array) { WriteNetworkSerializable(item); } } /// /// Writes a string /// /// The string to write /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void WriteValue(string s, bool oneByteChars = false) { WriteValue((uint)s.Length); int target = s.Length; if (oneByteChars) { for (int i = 0; i < target; ++i) { WriteByte((byte)s[i]); } } else { fixed (char* native = s) { WriteBytes((byte*)native, target * sizeof(char)); } } } /// /// Writes a string /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The string to write /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void WriteValueSafe(string s, bool oneByteChars = false) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } #endif int sizeInBytes = GetWriteSize(s, oneByteChars); if (!TryBeginWriteInternal(sizeInBytes)) { throw new OverflowException("Writing past the end of the buffer"); } WriteValue((uint)s.Length); int target = s.Length; if (oneByteChars) { for (int i = 0; i < target; ++i) { WriteByte((byte)s[i]); } } else { fixed (char* native = s) { WriteBytes((byte*)native, target * sizeof(char)); } } } /// /// Get the required size to write an unmanaged array /// /// The array to write /// The amount of elements to write /// Where in the array to start /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetWriteSize(T[] array, int count = -1, int offset = 0) where T : unmanaged { int sizeInTs = count != -1 ? count : array.Length - offset; int sizeInBytes = sizeInTs * sizeof(T); return sizeof(int) + sizeInBytes; } /// /// Write a partial value. The specified number of bytes is written from the value and the rest is ignored. /// /// Value to write /// Number of bytes /// Offset into the value to begin reading the bytes /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } if (Handle->Position + bytesToWrite > Handle->AllowedWriteMark) { throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()"); } #endif byte* ptr = ((byte*)&value) + offsetBytes; byte* bufferPointer = Handle->BufferPointer + Handle->Position; UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite); Handle->Position += bytesToWrite; } /// /// Write a byte to the stream. /// /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteByte(byte value) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } if (Handle->Position + 1 > Handle->AllowedWriteMark) { throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()"); } #endif Handle->BufferPointer[Handle->Position++] = value; } /// /// Write a byte to the stream. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteByteSafe(byte value) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } #endif if (!TryBeginWriteInternal(1)) { throw new OverflowException("Writing past the end of the buffer"); } Handle->BufferPointer[Handle->Position++] = value; } /// /// Write multiple bytes to the stream /// /// Value to write /// Number of bytes to write /// Offset into the buffer to begin writing [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytes(byte* value, int size, int offset = 0) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } if (Handle->Position + size > Handle->AllowedWriteMark) { throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()"); } #endif UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size); Handle->Position += size; } /// /// Write multiple bytes to the stream /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// Value to write /// Number of bytes to write /// Offset into the buffer to begin writing [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (Handle->InBitwiseContext) { throw new InvalidOperationException( "Cannot use BufferWriter in bytewise mode while in a bitwise context."); } #endif if (!TryBeginWriteInternal(size)) { throw new OverflowException("Writing past the end of the buffer"); } UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size); Handle->Position += size; } /// /// Write multiple bytes to the stream /// /// Value to write /// Number of bytes to write /// Offset into the buffer to begin writing [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytes(byte[] value, int size = -1, int offset = 0) { fixed (byte* ptr = value) { WriteBytes(ptr, size == -1 ? value.Length : size, offset); } } /// /// Write multiple bytes to the stream /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// Value to write /// Number of bytes to write /// Offset into the buffer to begin writing [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytesSafe(byte[] value, int size = -1, int offset = 0) { fixed (byte* ptr = value) { WriteBytesSafe(ptr, size == -1 ? value.Length : size, offset); } } /// /// Copy the contents of this writer into another writer. /// The contents will be copied from the beginning of this writer to its current position. /// They will be copied to the other writer starting at the other writer's current position. /// /// Writer to copy to [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void CopyTo(FastBufferWriter other) { other.WriteBytes(Handle->BufferPointer, Handle->Position); } /// /// Copy the contents of another writer into this writer. /// The contents will be copied from the beginning of the other writer to its current position. /// They will be copied to this writer starting at this writer's current position. /// /// Writer to copy to [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void CopyFrom(FastBufferWriter other) { WriteBytes(other.Handle->BufferPointer, other.Handle->Position); } /// /// Get the write size for any general unmanaged value /// The ForStructs value here makes this the lowest-priority overload so other versions /// will be prioritized over this if they match /// /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetWriteSize(in T value, ForStructs unused = default) where T : unmanaged { return sizeof(T); } /// /// Get the write size for a FixedString /// /// /// /// public static int GetWriteSize(in T value) where T : unmanaged, INativeList, IUTF8Bytes { return value.Length + sizeof(int); } /// /// Get the size required to write an unmanaged value of type T /// /// /// public static unsafe int GetWriteSize() where T : unmanaged { return sizeof(T); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanaged(in T value) where T : unmanaged { fixed (T* ptr = &value) { byte* bytes = (byte*)ptr; WriteBytes(bytes, sizeof(T)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanagedSafe(in T value) where T : unmanaged { fixed (T* ptr = &value) { byte* bytes = (byte*)ptr; WriteBytesSafe(bytes, sizeof(T)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanaged(T[] value) where T : unmanaged { WriteUnmanaged(value.Length); fixed (T* ptr = value) { byte* bytes = (byte*)ptr; WriteBytes(bytes, sizeof(T) * value.Length); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanagedSafe(T[] value) where T : unmanaged { WriteUnmanagedSafe(value.Length); fixed (T* ptr = value) { byte* bytes = (byte*)ptr; WriteBytesSafe(bytes, sizeof(T) * value.Length); } } /// /// This empty struct exists to allow overloading WriteValue based on generic constraints. /// At the bytecode level, constraints aren't included in the method signature, so if multiple /// methods exist with the same signature, it causes a compile error because they would end up /// being emitted as the same method, even if the constraints are different. /// Adding an empty struct with a default value gives them different signatures in the bytecode, /// which then allows the compiler to do overload resolution based on the generic constraints /// without the user having to pass the struct in themselves. /// public struct ForPrimitives { } /// /// This empty struct exists to allow overloading WriteValue based on generic constraints. /// At the bytecode level, constraints aren't included in the method signature, so if multiple /// methods exist with the same signature, it causes a compile error because they would end up /// being emitted as the same method, even if the constraints are different. /// Adding an empty struct with a default value gives them different signatures in the bytecode, /// which then allows the compiler to do overload resolution based on the generic constraints /// without the user having to pass the struct in themselves. /// public struct ForEnums { } /// /// This empty struct exists to allow overloading WriteValue based on generic constraints. /// At the bytecode level, constraints aren't included in the method signature, so if multiple /// methods exist with the same signature, it causes a compile error because they would end up /// being emitted as the same method, even if the constraints are different. /// Adding an empty struct with a default value gives them different signatures in the bytecode, /// which then allows the compiler to do overload resolution based on the generic constraints /// without the user having to pass the struct in themselves. /// public struct ForStructs { } /// /// This empty struct exists to allow overloading WriteValue based on generic constraints. /// At the bytecode level, constraints aren't included in the method signature, so if multiple /// methods exist with the same signature, it causes a compile error because they would end up /// being emitted as the same method, even if the constraints are different. /// Adding an empty struct with a default value gives them different signatures in the bytecode, /// which then allows the compiler to do overload resolution based on the generic constraints /// without the user having to pass the struct in themselves. /// public struct ForNetworkSerializable { } /// /// This empty struct exists to allow overloading WriteValue based on generic constraints. /// At the bytecode level, constraints aren't included in the method signature, so if multiple /// methods exist with the same signature, it causes a compile error because they would end up /// being emitted as the same method, even if the constraints are different. /// Adding an empty struct with a default value gives them different signatures in the bytecode, /// which then allows the compiler to do overload resolution based on the generic constraints /// without the user having to pass the struct in themselves. /// public struct ForFixedStrings { } /// /// Write a NetworkSerializable value /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); /// /// Write a NetworkSerializable array /// /// The values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); /// /// Write a NetworkSerializable value /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); /// /// Write a NetworkSerializable array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); /// /// Write a struct /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value); /// /// Write a struct array /// /// The values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value); /// /// Write a struct /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value); /// /// Write a struct array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value); /// /// Write 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 value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => WriteUnmanaged(value); /// /// Write 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 values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => WriteUnmanaged(value); /// /// Write 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 writes at once by calling TryBeginWrite. /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => WriteUnmanagedSafe(value); /// /// Write 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 writes at once by calling TryBeginWrite. /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable, IEquatable => WriteUnmanagedSafe(value); /// /// Write an enum value /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value); /// /// Write an enum array /// /// The values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value); /// /// Write an enum value /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value); /// /// Write an enum array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// The values to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value); /// /// Write a Vector2 /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Vector2 value) => WriteUnmanaged(value); /// /// Write a Vector2 array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Vector2[] value) => WriteUnmanaged(value); /// /// Write a Vector3 /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Vector3 value) => WriteUnmanaged(value); /// /// Write a Vector3 array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Vector3[] value) => WriteUnmanaged(value); /// /// Write a Vector2Int /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Vector2Int value) => WriteUnmanaged(value); /// /// Write a Vector2Int array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value); /// /// Write a Vector3Int /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Vector3Int value) => WriteUnmanaged(value); /// /// Write a Vector3Int array /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value); /// /// Write a Vector4 /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Vector4 value) => WriteUnmanaged(value); /// /// Write a Vector4 /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Vector4[] value) => WriteUnmanaged(value); /// /// Write a Quaternion /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Quaternion value) => WriteUnmanaged(value); /// /// Write a Quaternion array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Quaternion[] value) => WriteUnmanaged(value); /// /// Write a Color /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Color value) => WriteUnmanaged(value); /// /// Write a Color array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Color[] value) => WriteUnmanaged(value); /// /// Write a Color32 /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Color32 value) => WriteUnmanaged(value); /// /// Write a Color32 array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Color32[] value) => WriteUnmanaged(value); /// /// Write a Ray /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Ray value) => WriteUnmanaged(value); /// /// Write a Ray array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Ray[] value) => WriteUnmanaged(value); /// /// Write a Ray2D /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(in Ray2D value) => WriteUnmanaged(value); /// /// Write a Ray2D array /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(Ray2D[] value) => WriteUnmanaged(value); /// /// Write a Vector2 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value); /// /// Write a Vector2 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value); /// /// Write a Vector3 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value); /// /// Write a Vector3 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value); /// /// Write a Vector2Int /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value); /// /// Write a Vector2Int array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value); /// /// Write a Vector3Int /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value); /// /// Write a Vector3Int array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value); /// /// Write a Vector4 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value); /// /// Write a Vector4 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value); /// /// Write a Quaternion /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value); /// /// Write a Quaternion array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value); /// /// Write a Color /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value); /// /// Write a Collor array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value); /// /// Write a Color32 /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value); /// /// Write a Color32 array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value); /// /// Write a Ray /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value); /// /// Write a Ray array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value); /// /// Write a Ray2D /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value); /// /// Write a Ray2D array /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the values to write [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(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. /// /// Write a FixedString value. Writes only the part of the string that's actually used. /// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling /// FastBufferWriter.GetWriteSize()) /// /// the value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteValue(in T value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { WriteUnmanaged(value.Length); // This avoids a copy on the string, which could be costly for FixedString4096Bytes // Otherwise, GetUnsafePtr() is an impure function call and will result in a copy // for `in` parameters. fixed (T* ptr = &value) { WriteBytes(ptr->GetUnsafePtr(), value.Length); } } /// /// Write a FixedString value. Writes only the part of the string that's actually used. /// /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking /// for multiple writes at once by calling TryBeginWrite. /// /// the value to write /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in T value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { if (!TryBeginWriteInternal(sizeof(int) + value.Length)) { throw new OverflowException("Writing past the end of the buffer"); } WriteValue(value); } } }