rd-net/Lifetimes/Serialization/UnsafeWriter.cs (781 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using JetBrains.Annotations;
using JetBrains.Diagnostics;
using JetBrains.Util;
using JetBrains.Util.Internal;
// ReSharper disable BuiltInTypeReferenceStyle
// ReSharper disable ArrangeRedundantParentheses
namespace JetBrains.Serialization
{
/// <summary>
/// Effective serialization tool. <see cref="UnsafeWriter"/> is thread-bound automatically expandable and shrinkable
/// byte buffer (with initial size of 1Mb). Entry point is <see cref="Cookie"/> obtained by <see cref="NewThreadLocalWriter"/>.
/// It is <see cref="IDisposable"/> so must be used only with (possibly nested) <c>using</c> in stack-like way.
/// <see cref="Cookie"/> contains start position and length of currently serialized data (start + len = position), so when disposed it reverts writer
/// position to the cookie's start position.
/// <seealso cref="UnsafeReader"/>
/// </summary>
[PublicAPI]
public sealed unsafe class UnsafeWriter
{
private readonly int myInitialAllocSize;
[PublicAPI]
public readonly struct Cookie : IDisposable
{
private readonly UnsafeWriter myWriter;
private readonly int myStart;
public Cookie(UnsafeWriter writer)
{
myWriter = writer;
myWriter.Initialize();
myStart = myWriter.Count;
}
public UnsafeWriter Writer
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myWriter;
}
public byte* Data
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myWriter.Data + myStart;
}
public int Count
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myWriter.Count - myStart;
}
public byte[] CloneData()
{
var res = new byte[Count];
Marshal.Copy((IntPtr)Data, res, 0, Count);
return res;
}
public void CopyTo(byte[] dst, [Optional] int dstOffset, [Optional] int? count)
{
CopyTo(dst, dstOffset, count ?? Count);
}
public void CopyTo(byte[] dst, int dstOffset, int count)
{
CopyTo(dst, 0, dstOffset, count);
}
public void CopyTo(byte[] dst, int srcOffset, int dstOffset, int count)
{
Marshal.Copy((IntPtr)(Data + srcOffset), dst, dstOffset, count);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Dispose()
{
// default struct constructor was used for unknown reason
if (myWriter == null)
return;
myWriter.Deinitialize(myStart);
}
}
/// <summary>
/// A bookmark to the offset in the memory block of the <see cref="UnsafeWriter"/>. Can be used for saving address
/// in UnsafeWriter for future use. Basically every method in <see cref="UnsafeWriter"/> can cause reallocation of
/// data. It is important to avoid storing pointers obtained from UnsafeWriter between these calls.
/// </summary>
[PublicAPI]
public readonly struct Bookmark
{
private readonly UnsafeWriter myWriter;
private readonly int myStart;
public Bookmark(UnsafeWriter writer)
{
myWriter = writer;
myStart = myWriter.Count;
}
public byte* Data
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myWriter.Data + myStart;
}
/// <summary>
/// Writes `<see cref="Count"/><c> - sizeof(int)</c>` into the <see cref="Data"/> pointer.
/// Cookie must be prepared by invoking `<see cref="UnsafeWriter.WriteInt32(int)"/><c>(0)</c>` as first cookie call.
/// </summary>
public void WriteIntLength()
{
*((int*)Data) = myWriter.Count - myStart - sizeof(int);
}
public void WriteIntLength(int length)
{
*((int*)Data) = length;
}
public void FinishRawWrite(int bytesWritten)
{
if (bytesWritten <= 0)
throw new ArgumentOutOfRangeException(nameof(bytesWritten));
var finalPtr = myWriter.myStartPtr + myStart + bytesWritten;
if (myWriter.myPtr <= finalPtr)
throw new ArgumentOutOfRangeException(nameof(bytesWritten), "Overflow, allocation is smaller then bytes written");
myWriter.myPtr = finalPtr;
myWriter.myCount = myStart + bytesWritten;
}
public void Reset()
{
FinishRawWrite(myStart - myWriter.Count);
}
}
private const string LogCategory = "UnsafeWriter";
/// <summary>
/// Whether <see cref="UnsafeWriter"/> can be cached for the specific thread.
/// </summary>
[Obsolete("Don't use")]
public static bool AllowUnsafeWriterCaching
{
get => true;
// ReSharper disable once ValueParameterNotUsed
set { }
}
/// <summary>
/// Cached <see cref="UnsafeWriter"/> for reuse
/// </summary>
[ThreadStatic] private static UnsafeWriter? ourWriter;
public static Cookie NewThreadLocalWriter()
{
if (ourWriter != null)
return new Cookie(ourWriter);
ourWriter = new UnsafeWriter();
var writer = new Cookie(ourWriter);
return writer;
}
[Obsolete("Use NewThreadLocalWriter()")]
public static Cookie NewThreadLocalWriterNoCaching()
{
return NewThreadLocalWriterImpl(false);
}
[Obsolete("Use NewThreadLocalWriter()")]
private static Cookie NewThreadLocalWriterImpl(bool allowCaching)
{
return NewThreadLocalWriter();
}
private byte* myStartPtr;
private int myCurrentAllocSize;
private byte* myPtr;
private int myCount;
/// <summary>
/// Indicates whether the UnsafeWriter should try to cleanup used memory in <see cref="NativeMemoryPool"/>
/// </summary>
internal int ReleaseResources;
private UnsafeWriter()
{
myInitialAllocSize = NativeMemoryPool.AllocSize;
myMemory = null;
}
/// <summary>
/// Stores the last used memory holder. Be aware that this holder is not reserved for current unsafe writer only and
/// in some circumstances may be used and reserved by other consumer (when it's free)
/// </summary>
private NativeMemoryPool.ThreadMemoryHolder? myMemory;
private int myRecursionLevel;
/// <summary>
/// Creates a new UnsafeWriter
/// </summary>
private void Initialize()
{
if (myRecursionLevel++ == 0)
{
if (myMemory != null && myMemory.TryReserve())
{
}
else
{
var cookie = NativeMemoryPool.ReserveMiss();
if (Mode.IsAssertion) Assertion.Assert(cookie.IsValid);
myMemory = cookie.myHolder;
if (cookie.CausedAllocation)
{
ReleaseResources++;
}
}
myCurrentAllocSize = NativeMemoryPool.AllocSize;
myStartPtr = myPtr = (byte*) myMemory.Data;
myCount = 0;
}
}
private void Deinitialize(int start)
{
if (--myRecursionLevel == 0)
{
myMemory!.Free();
myCurrentAllocSize = 0;
// Setting current alloc size to zero have a special semantic of making current UnsafeWriter invalid.
// There is no need to additionally resetting these pointers as write will check available memory and raise an
// exception for this special case
// myStartPtr = (byte*) 0;
// myPtr = (byte*) 0;
}
else
{
Reset(start);
}
}
~UnsafeWriter()
{
for (var index = 0; index < ReleaseResources; index++)
{
NativeMemoryPool.TryFreeMemory();
}
}
private void Reset(int start = 0)
{
myPtr = myStartPtr + start;
myCount = start;
if (myCurrentAllocSize > myInitialAllocSize)
{
if (start == 0) Realloc(myCount);
else LogLog.Verbose(LogCategory, "Can't realloc, start={0}", start);
}
}
private int Count
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myCount;
}
private byte* Data
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myStartPtr;
}
public byte* Ptr
{
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
get => myPtr;
}
private void Prepare(int nbytes)
{
var newCount = myCount + nbytes;
if (newCount <= myCurrentAllocSize)
{
myCount = newCount;
return;
}
Realloc(newCount);
}
private void Realloc(int newCount)
{
if (myCurrentAllocSize == 0)
throw new InvalidOperationException("UnsafeWriter is uninitialized, unable to realloc");
var reallocSize = myInitialAllocSize;
while (newCount > reallocSize)
{
reallocSize <<= 1;
if (reallocSize <= 0)
{
reallocSize = NativeMemoryPool.MaxAllocSize;
}
}
try
{
LogLog.Verbose(LogCategory, "Realloc UnsafeWriter, current: {0:N0} bytes, new: {1:N0}", myCurrentAllocSize, reallocSize);
if (myStartPtr != null) //already terminated
{
Assertion.AssertNotNull(myMemory);
myStartPtr = (byte*) myMemory.Realloc(reallocSize);
myPtr = myStartPtr + myCount;
myCurrentAllocSize = reallocSize;
myCount = newCount;
}
}
catch (Exception exception)
{
throw new ArgumentException(
$"Can't allocate more memory for chunk: {reallocSize} bytes, currentlyAllocated={myCurrentAllocSize}, count={myCount}", exception);
}
}
#region Primitive writers
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteBool() instead",
ReplaceTemplate = "$qualifier$.WriteBoolean($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(bool value) => WriteBoolean(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteByte() instead",
ReplaceTemplate = "$qualifier$.WriteByte($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(byte value) => WriteByte(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteGuid() instead",
ReplaceTemplate = "$qualifier$.WriteGuid($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(Guid value) => WriteGuid(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteChar() instead",
ReplaceTemplate = "$qualifier$.WriteChar($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(char value) => WriteChar(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteDecimal() instead",
ReplaceTemplate = "$qualifier$.WriteDecimal($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(decimal value) => WriteDecimal(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteDouble() instead",
ReplaceTemplate = "$qualifier$.WriteDouble($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(double value) => WriteDouble(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteFloat() instead",
ReplaceTemplate = "$qualifier$.WriteFloat($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(float value) => WriteFloat(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteInt16() instead",
ReplaceTemplate = "$qualifier$.WriteInt16($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(Int16 value) => WriteInt16(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteInt32() instead",
ReplaceTemplate = "$qualifier$.WriteInt32($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(Int32 value) => WriteInt32(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteInt64() instead",
ReplaceTemplate = "$qualifier$.WriteInt64($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(Int64 value) => WriteInt64(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteUInt16() instead",
ReplaceTemplate = "$qualifier$.WriteUInt16($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(UInt16 value) => WriteUInt16(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteUInt32() instead",
ReplaceTemplate = "$qualifier$.WriteUInt32($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(UInt32 value) => WriteUInt32(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteUInt64() instead",
ReplaceTemplate = "$qualifier$.WriteUInt64($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(UInt64 value) => WriteUInt64(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteDateTime() instead",
ReplaceTemplate = "$qualifier$.WriteDateTime($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(DateTime value) => WriteDateTime(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteTimeSpan() instead",
ReplaceTemplate = "$qualifier$.WriteTimeSpan($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(TimeSpan value) => WriteTimeSpan(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteUri() instead",
ReplaceTemplate = "$qualifier$.WriteUri($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(Uri value) => WriteUri(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteString() instead",
ReplaceTemplate = "$qualifier$.WriteString($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(string? value) => WriteString(value);
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteBoolean(bool value)
{
Prepare(sizeof(byte));
*(myPtr++) = (byte)(value ? 1 : 0);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteByte(byte value)
{
Prepare(sizeof(byte));
*(myPtr++) = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteSByte(sbyte value)
{
Prepare(sizeof(sbyte));
*(myPtr++) = (byte) value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteGuid(Guid value)
{
Write<byte, byte[]>((writer, b) => writer.WriteByte(b), value.ToByteArray());
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteChar(char value)
{
Prepare(sizeof(char));
var x = (char*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteDecimal(decimal value)
{
Prepare(sizeof(decimal));
var x = (decimal*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteDouble(double value)
{
Prepare(sizeof(double));
var x = (double*)myPtr;
myPtr = (byte*)(x + 1);
if (!RuntimeInfo.IsUnalignedAccessAllowed)
{
Buffer.MemoryCopy(&value, x, sizeof(double), sizeof(double));
}
else
{
*x = value;
}
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteFloat(float value)
{
Prepare(sizeof(float));
var x = (float*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteInt16(Int16 value)
{
Prepare(sizeof(Int16));
var x = (Int16*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteInt32(Int32 value)
{
Prepare(sizeof(Int32));
var x = (Int32*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
public void Write7BitEncodedInt32(int value)
{
// write out an int 7 bits at a time
var v = (uint)value;
while (v >= 0b10000000)
{
WriteByte((byte)(v | 0b10000000));
v >>= 7;
}
WriteByte((byte)v);
}
public void WriteOftenSmallPositiveInt32(int value)
{
if (value >= 0 & value < byte.MaxValue)
{
WriteByte((byte) value);
}
else
{
WriteByte(byte.MaxValue);
WriteInt32(value);
}
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public static void WriteInt32ToBytes(Int32 value, byte[] data, int offset)
{
fixed (byte* bb = data)
*(int*)(bb+offset) = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteInt64(Int64 value)
{
Prepare(sizeof(Int64));
var x = (Int64*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[Obsolete("Use 'WriteUInt16' instead (correct casing)")]
public void WriteUint16(UInt16 value) => WriteUInt16(value);
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteUInt16(UInt16 value)
{
Prepare(sizeof(UInt16));
var x = (UInt16*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteUInt32(UInt32 value)
{
Prepare(sizeof(UInt32));
var x = (UInt32*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[Obsolete("Use 'WriteUInt64' instead (correct casing)")]
public void WriteUint64(UInt64 value) => WriteUInt64(value);
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteUInt64(UInt64 value)
{
Prepare(sizeof(UInt64));
var x = (UInt64*)myPtr;
myPtr = (byte*)(x + 1);
*x = value;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteDateTime(DateTime value)
{
if (Mode.IsAssertion) Assertion.Assert(value.Kind != DateTimeKind.Local, "Use UTC time");
WriteInt64(value.Ticks);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteTimeSpan(TimeSpan value)
{
WriteInt64(value.Ticks);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteUri(Uri value)
{
#pragma warning disable SYSLIB0013
WriteString(Uri.EscapeUriString(value.OriginalString));
#pragma warning restore SYSLIB0013
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteString(string? value)
{
if (value == null)
{
WriteInt32(-1);
}
else
{
WriteInt32(value.Length);
WriteStringContentInternal(this, value, 0, value.Length);
}
}
public void WriteStringUTF8(string? value)
{
if (value == null)
{
WriteByte(0); // mean null
}
else if (value.Length == 0)
{
WriteByte(1); // means empty string
}
else // non-empty string
{
var maxBytesForString = Encoding.UTF8.GetMaxByteCount(value.Length);
var bytesForLength = maxBytesForString < 254 ? 1 : 5; // [byte <254 bytes_count] or [0xFF marker]+[int32 bytes_count]
var bookmark = Alloc(maxBytesForString + bytesForLength);
fixed (char* sourcePtr = value)
{
var bytesWritten = Encoding.UTF8.GetBytes(
sourcePtr, charCount: value.Length, bytes: bookmark.Data + bytesForLength, maxBytesForString);
if (bytesForLength == 1) // [byte bytes_count]+[utf8 bytes]
{
if (Mode.IsAssertion) Assertion.Assert(bytesWritten < 254);
*bookmark.Data = (byte)(bytesWritten + 1);
}
else // [0xFF byte]+[int32 bytes_count]+[utf8 bytes]
{
*bookmark.Data = 0xFF;
*(int*)(bookmark.Data + 1) = bytesWritten;
}
bytesWritten += bytesForLength;
bookmark.FinishRawWrite(bytesWritten);
}
}
}
/// <summary>
/// Doesn't write length prefix, only string contents. If <paramref name="value"/> is <c>value</c>, does nothing.
/// </summary>
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteStringContent(string? value)
{
if (value == null) return;
WriteStringContentInternal(this, value, 0, value.Length);
}
/// <summary>
/// Doesn't write length prefix, only string contents. If <paramref name="value"/> is <c>value</c>, does nothing.
/// </summary>
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void WriteStringContent(string? value, int offset, int count)
{
if (value == null) return;
if (offset < 0 || count < 0 || offset + count > value.Length)
throw new ArgumentException($"string.length={value.Length}, offset={offset}, count={count}");
WriteStringContentInternal(this, value, offset, count);
}
private static readonly bool ourOldMonoFlag = RuntimeInfo.CurrentMonoVersion != null
&& !(RuntimeInfo.CurrentMonoVersion.Major >= 5
&& RuntimeInfo.CurrentMonoVersion.Minor >= 8);
/*
It is special method to avoid crash on mono before 5.0
Additional info details see on GitHub: https://github.com/mono/mono/pull/6020
of bugzilla: https://bugzilla.xamarin.com/show_bug.cgi?id=60625
It is shouldn't dropped while we support client mono version before 5.0
*/
private static void WriteStringContentInternal(UnsafeWriter writer, string value, int offset, int count)
{
if (ourOldMonoFlag)
{
WriteStringContentInternalBeforeMono5(writer, value, offset, count);
}
else
{
WriteStringContentInternalAfterMono5(writer, value, offset, count);
}
}
// Mono 5.4 tries to inline this method and crashes.
// [MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
private static void WriteStringContentInternalAfterMono5(UnsafeWriter writer, string value, int offset, int count)
{
fixed (char* c = value)
{
writer.Write((byte*) (c + offset), count * sizeof(char));
}
}
private static void WriteStringContentInternalBeforeMono5(UnsafeWriter writer, string value, int offset, int count)
{
for (var index = offset; index < offset + count; index++)
{
writer.WriteChar(value[index]);
}
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public void Write(byte* ptr, int size)
{
Prepare(size);
Memory.CopyMemory(ptr, myPtr, size);
myPtr += size;
}
#endregion
#region Delegates
public delegate void WriteDelegate<in T>(UnsafeWriter writer, T value);
public static readonly WriteDelegate<bool> BooleanDelegate = (writer, x) => writer.WriteBoolean(x);
public static readonly WriteDelegate<byte> ByteDelegate = (writer, x) => writer.WriteByte(x);
public static readonly WriteDelegate<Guid> GuidDelegate = (writer, x) => writer.WriteGuid(x);
public static readonly WriteDelegate<char> CharDelegate = (writer, x) => writer.WriteChar(x);
public static readonly WriteDelegate<decimal> DecimalDelegate = (writer, x) => writer.WriteDecimal(x);
public static readonly WriteDelegate<double> DoubleDelegate = (writer, x) => writer.WriteDouble(x);
public static readonly WriteDelegate<float> FloatDelegate = (writer, x) => writer.WriteFloat(x);
public static readonly WriteDelegate<Int16> Int16Delegate = (writer, x) => writer.WriteInt16(x);
public static readonly WriteDelegate<Int32> Int32Delegate = (writer, x) => writer.WriteInt32(x);
public static readonly WriteDelegate<Int64> Int64Delegate = (writer, x) => writer.WriteInt64(x);
public static readonly WriteDelegate<UInt16> UInt16Delegate = (writer, x) => writer.WriteUInt16(x);
public static readonly WriteDelegate<UInt32> UInt32Delegate = (writer, x) => writer.WriteUInt32(x);
public static readonly WriteDelegate<UInt64> UInt64Delegate = (writer, x) => writer.WriteUInt64(x);
public static readonly WriteDelegate<DateTime> DateTimeDelegate = (writer, x) => writer.WriteDateTime(x);
public static readonly WriteDelegate<Uri> UriDelegate = (writer, x) => writer.WriteUri(x);
public static readonly WriteDelegate<string> StringDelegate = (writer, x) => writer.WriteString(x);
public static readonly WriteDelegate<byte[]> ByteArrayDelegate = (writer, x) => writer.WriteByteArray(x);
public static readonly WriteDelegate<int[]> IntArrayDelegate = (writer, x) => writer.WriteArray(x);
public static readonly WriteDelegate<string[]> StringArrayDelegate = (writer, x) => writer.WriteCollection(StringDelegate, x);
#endregion
#region Collection writers
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteArray() instead",
ReplaceTemplate = "$qualifier$.WriteArray($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
public void Write(int[]? value) => WriteArray(value);
[CodeTemplate(
searchTemplate: "$member$($arg$)",
Message = "HINT: Use WriteByteArray() instead",
ReplaceTemplate = "$qualifier$.WriteByteArray($arg$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
public void Write(byte[]? value) => WriteByteArray(value);
public void WriteArray(int[]? value)
{
if (value == null)
{
WriteInt32(-1);
}
else
{
WriteInt32(value.Length);
fixed (int* c = value)
{
Write((byte*)c, value.Length * sizeof(int));
}
}
}
public void WriteByteArray(byte[]? value)
{
if (value == null)
{
WriteInt32(-1);
}
else
{
var size = value.Length;
WriteInt32(size);
Prepare(size);
Marshal.Copy(value, 0, (IntPtr)myPtr, size); // Unlike MemoryUtil::CopyMemory, this is a CLR intrinsic call
myPtr += size;
}
}
public void WriteRaw(byte[] value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
var size = value.Length;
Prepare(size);
Marshal.Copy(value, 0, (IntPtr)myPtr, size); // Unlike MemoryUtil::CopyMemory, this is a CLR intrinsic call
myPtr += size;
}
public void WriteRaw(byte[] value, int start, int length)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
Prepare(length);
Marshal.Copy(value, start, (IntPtr)myPtr, length); // Unlike MemoryUtil::CopyMemory, this is a CLR intrinsic call
myPtr += length;
}
/// <summary>
/// Creates <see cref="Bookmark"/> for the current <see cref="UnsafeWriter"/>'s position.
/// </summary>
[MustUseReturnValue]
public Bookmark MakeBookmark()
{
return new Bookmark(this);
}
/// <summary>
/// Correctly allocates the number of bytes as if they were written with any func, and advances the pointer past them.
/// This is useful if you want to use buffer space for direct memory access.
/// </summary>
/// <remarks>
/// Never save the value of <see cref="Ptr" /> before calling <see cref="Alloc" />! This method may cause a reallocation
/// of data after which the saved pointer became invalid.
/// </remarks>
/// <returns><see cref="Bookmark"/> to the allocated buffer</returns>
public Bookmark Alloc(int length)
{
var result = new Bookmark(this);
Prepare((int)checked((uint)length));
myPtr += length;
return result;
}
[CodeTemplate(
searchTemplate: "$member$($args$)",
Message = "HINT: Use WriteCollection() instead",
ReplaceTemplate = "$qualifier$.WriteCollection($args$)",
SuppressionKey = "UnsafeWriter_ExplicitApi")]
public void Write<T, TCollection>(WriteDelegate<T> writeDelegate, TCollection? value)
where TCollection : ICollection<T>
=> WriteCollection(writeDelegate, value);
/// <summary>
/// Non optimal collection serialization. You can serialize internal structure (eg. array) instead.
/// </summary>
public void WriteCollection<T, TCollection>(WriteDelegate<T> writeDelegate, TCollection? value)
where TCollection : ICollection<T>
{
if (value == null)
{
WriteInt32(-1);
}
else
{
WriteInt32(value.Count);
foreach (var x in value)
{
writeDelegate(this, x);
}
}
}
public void Write<TK, TV, TDictionary>(
WriteDelegate<TK> writeKeyDelegate, WriteDelegate<TV> writeValueDelegate, TDictionary? value)
where TDictionary : IDictionary<TK, TV>
{
if (value == null)
{
WriteInt32(-1);
}
else
{
WriteInt32(value.Count);
foreach (var kv in value)
{
writeKeyDelegate(this, kv.Key);
writeValueDelegate(this, kv.Value);
}
}
}
#endregion
[ContractAnnotation("null=>false")]
public bool WriteNullness<T>([NotNullWhen(true)] T? value) where T : struct
{
var res = value != null;
WriteBoolean(res);
return res;
}
[ContractAnnotation("null=>false")]
public bool WriteNullness<T>([NotNullWhen(true)] T? value) where T : class
{
var res = value != null;
WriteBoolean(res);
return res;
}
}
}