~ruther/NosSmooth

762fc34c97e8b1f6e9290426b8bc2a78122e28ba — Rutherther 2 years ago 58d8743 + 22e4c56
Merge pull request #82 from Rutherther/feat/serialize-stackalloc

Use stackalloc and Span for packet serialization
32 files changed, 167 insertions(+), 111 deletions(-)

M Core/NosSmooth.Core/NosSmooth.Core.csproj
M Packets/NosSmooth.PacketSerializer.Abstractions/BaseStringConverter.cs
M Packets/NosSmooth.PacketSerializer.Abstractions/IStringConverter.cs
M Packets/NosSmooth.PacketSerializer.Abstractions/IStringSerializer.cs
M Packets/NosSmooth.PacketSerializer.Abstractions/NosSmooth.PacketSerializer.Abstractions.csproj
M Packets/NosSmooth.PacketSerializer.Abstractions/PacketStringBuilder.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/BasicTypeConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/BoolStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/ByteStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/IntStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/LongStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/ShortStringConverter.cs
A Packets/NosSmooth.PacketSerializer/Converters/Basic/SpanFormattableTypeConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/UIntStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/ULongStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Basic/UShortStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Common/NameStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Packets/UpgradeRareSubPacketConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/EnumStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/ListStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/NullableStringConverter.cs
M Packets/NosSmooth.PacketSerializer/Converters/Special/StringSerializer.cs
M Packets/NosSmooth.PacketSerializer/NosSmooth.PacketSerializer.csproj
M Packets/NosSmooth.PacketSerializer/PacketSerializer.cs
M Packets/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/FallbackInlineConverterGenerator.cs
M Packets/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs
M Packets/NosSmooth.Packets/NosSmooth.Packets.csproj
M Tests/NosSmooth.Packets.Tests/Converters/Basic/BoolStringConverterTests.cs
M Tests/NosSmooth.Packets.Tests/Converters/Basic/StringConverterTests.cs
M Tests/NosSmooth.Packets.Tests/PacketStringBuilderTests.cs
M Core/NosSmooth.Core/NosSmooth.Core.csproj => Core/NosSmooth.Core/NosSmooth.Core.csproj +1 -1
@@ 3,11 3,11 @@
    <PropertyGroup>
        <Nullable>enable</Nullable>
        <LangVersion>10</LangVersion>
        <TargetFrameworks>net7.0;netstandard2.1</TargetFrameworks>
        <Authors>Rutherther</Authors>
        <Description>NosSmooth Core library allowing implementing nostale client, handling packets and commands.</Description>
        <VersionPrefix>5.0.0</VersionPrefix>
        <PackageReleaseNotes>Move PetWalkCommand to MateWalkCommand.</PackageReleaseNotes>
        <TargetFramework>net7.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>

M Packets/NosSmooth.PacketSerializer.Abstractions/BaseStringConverter.cs => Packets/NosSmooth.PacketSerializer.Abstractions/BaseStringConverter.cs +3 -3
@@ 16,7 16,7 @@ namespace NosSmooth.PacketSerializer.Abstractions;
public abstract class BaseStringConverter<TParseType> : IStringConverter<TParseType>
{
    /// <inheritdoc />
    public abstract Result Serialize(TParseType? obj, PacketStringBuilder builder);
    public abstract Result Serialize(TParseType? obj, ref PacketStringBuilder builder);

    /// <inheritdoc />
    public abstract Result<TParseType?> Deserialize(ref PacketStringEnumerator stringEnumerator, DeserializeOptions options);


@@ 34,13 34,13 @@ public abstract class BaseStringConverter<TParseType> : IStringConverter<TParseT
    }

    /// <inheritdoc/>
    Result IStringConverter.Serialize(object? obj, PacketStringBuilder builder)
    Result IStringConverter.Serialize(object? obj, ref PacketStringBuilder builder)
    {
        if (!(obj is TParseType parseType))
        {
            return new WrongTypeError(this, typeof(TParseType), obj);
        }

        return Serialize(parseType, builder);
        return Serialize(parseType, ref builder);
    }
}

M Packets/NosSmooth.PacketSerializer.Abstractions/IStringConverter.cs => Packets/NosSmooth.PacketSerializer.Abstractions/IStringConverter.cs +2 -2
@@ 27,7 27,7 @@ public interface IStringConverter
    /// <param name="obj">The object to serialize.</param>
    /// <param name="builder">The string builder to append to.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize(object? obj, PacketStringBuilder builder);
    public Result Serialize(object? obj, ref PacketStringBuilder builder);
}

/// <summary>


@@ 53,5 53,5 @@ public interface IStringConverter<TParseType> : IStringConverter
    /// <param name="obj">The object to serialize.</param>
    /// <param name="builder">The string builder to append to.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize(TParseType? obj, PacketStringBuilder builder);
    public Result Serialize(TParseType? obj, ref PacketStringBuilder builder);
}
\ No newline at end of file

M Packets/NosSmooth.PacketSerializer.Abstractions/IStringSerializer.cs => Packets/NosSmooth.PacketSerializer.Abstractions/IStringSerializer.cs +2 -2
@@ 29,7 29,7 @@ public interface IStringSerializer
    /// <param name="obj">The object to serialize.</param>
    /// <param name="builder">The string builder to append to.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize(Type parseType, object? obj, PacketStringBuilder builder);
    public Result Serialize(Type parseType, object? obj, ref PacketStringBuilder builder);

    /// <summary>
    /// Convert the data from the enumerator to the given type.


@@ 47,5 47,5 @@ public interface IStringSerializer
    /// <param name="builder">The string builder to append to.</param>
    /// <typeparam name="TParseType">The type of the object to deserialize.</typeparam>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize<TParseType>(TParseType? obj, PacketStringBuilder builder);
    public Result Serialize<TParseType>(TParseType? obj, ref PacketStringBuilder builder);
}
\ No newline at end of file

M Packets/NosSmooth.PacketSerializer.Abstractions/NosSmooth.PacketSerializer.Abstractions.csproj => Packets/NosSmooth.PacketSerializer.Abstractions/NosSmooth.PacketSerializer.Abstractions.csproj +1 -1
@@ 6,8 6,8 @@
        <LangVersion>10</LangVersion>
        <Description>NosSmooth's packet serializer abstractions that hold all interfaces, classes, errors needed for creating assemblies with packets that can have generated serializers.</Description>
        <VersionPrefix>1.3.2</VersionPrefix>
        <TargetFrameworks>net7.0;netstandard2.1</TargetFrameworks>
        <PackageReleaseNotes>Accept ReadOnlySpan in PacketStringEnumerator.</PackageReleaseNotes>
        <TargetFramework>net7.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>

M Packets/NosSmooth.PacketSerializer.Abstractions/PacketStringBuilder.cs => Packets/NosSmooth.PacketSerializer.Abstractions/PacketStringBuilder.cs +85 -65
@@ 4,6 4,7 @@
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Buffers;
using System.Text;
using Remora.Results;



@@ 12,21 13,26 @@ namespace NosSmooth.PacketSerializer.Abstractions;
/// <summary>
/// String builder for packets.
/// </summary>
public class PacketStringBuilder
public ref struct PacketStringBuilder
{
    private readonly StringBuilder _builder;
    private Span<char> _buffer;
    private char[]? _bufferArray;
    private int _position;
    private StringBuilderLevel _currentLevel;
    private char? _insertSeparator;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketStringBuilder"/> class.
    /// Initializes a new instance of the <see cref="PacketStringBuilder"/> struct.
    /// </summary>
    /// <param name="initialBuffer">The initial buffer to store the packet to. Will grow in size if needed.</param>
    /// <param name="separator">The top level separator.</param>
    public PacketStringBuilder(char separator = ' ')
    public PacketStringBuilder(Span<char> initialBuffer, char separator = ' ')
    {
        _currentLevel = new StringBuilderLevel(null, separator);
        _insertSeparator = null;
        _builder = new StringBuilder();
        _buffer = initialBuffer;
        _position = 0;
        _bufferArray = null;
    }

    /// <summary>


@@ 113,24 119,28 @@ public class PacketStringBuilder
    }

    /// <summary>
    /// Appends the value to the string.
    /// Appends a value that is span formattable.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(string value)
    /// <typeparam name="T">The span formattable type.</typeparam>
    public void Append<T>(T value)
        where T : ISpanFormattable
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
        AppendSpanFormattable(value);
    }

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(int value)
    public void Append(ReadOnlySpan<char> value)
    {
        BeforeAppend();
        _builder.Append(value);
        while (!value.TryCopyTo(_buffer.Slice(_position)))
        {
            GrowBuffer(value.Length);
        }
        _position += value.Length;
        AfterAppend();
    }



@@ 138,128 148,138 @@ public class PacketStringBuilder
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(int value)
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(uint value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(short value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(char value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(ushort value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(long value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(ulong value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(byte value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(sbyte value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(float value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(double value)
    {
        BeforeAppend();
        _builder.Append(value);
        AfterAppend();
    }
        => AppendSpanFormattable(value);

    /// <summary>
    /// Appends the value to the string.
    /// </summary>
    /// <param name="value">The value to append.</param>
    public void Append(decimal value)
        => AppendSpanFormattable(value);

    /// <summary>
    /// Returns buffer to ArrayPool if it has been used.
    /// </summary>
    public void Dispose()
    {
        if (_bufferArray is not null)
        {
            ArrayPool<char>.Shared.Return(_bufferArray);
        }
    }

    private void AppendSpanFormattable<T>(T value)
        where T : ISpanFormattable
    {
        BeforeAppend();
        _builder.Append(value);
        int charsWritten;
        while (!value.TryFormat(_buffer.Slice(_position), out charsWritten, default, null))
        {
            GrowBuffer();
        }
        _position += charsWritten;
        AfterAppend();
    }

    private void GrowBuffer(int needed = 0)
    {
        var sizeNeeded = _buffer.Length + needed;
        var doubleSize = _buffer.Length * 2;
        var newSize = Math.Max(doubleSize, sizeNeeded);
        var newBuffer = ArrayPool<char>.Shared.Rent(newSize);

        _buffer.CopyTo(newBuffer);
        _buffer = newBuffer;

        if (_bufferArray is not null)
        {
            ArrayPool<char>.Shared.Return(_bufferArray);
        }
        _bufferArray = newBuffer;
    }

    private void BeforeAppend()
    {
        if (_insertSeparator is not null)
        {
            _builder.Append(_insertSeparator);
            if (_buffer.Length <= _position + 1)
            {
                GrowBuffer();
            }

            _buffer[_position] = _insertSeparator.Value;
            _position += 1;
            _insertSeparator = null;
        }
    }


@@ 288,7 308,7 @@ public class PacketStringBuilder
    /// <inheritdoc />
    public override string ToString()
    {
        return _builder.ToString();
        return _buffer.Slice(0, _position).ToString();
    }

    private class StringBuilderLevel

M Packets/NosSmooth.PacketSerializer/Converters/Basic/BasicTypeConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/BasicTypeConverter.cs +1 -1
@@ 17,7 17,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
public abstract class BasicTypeConverter<TBasicType> : BaseStringConverter<TBasicType>
{
    /// <inheritdoc />
    public override Result Serialize(TBasicType? obj, PacketStringBuilder builder)
    public override Result Serialize(TBasicType? obj, ref PacketStringBuilder builder)
    {
        builder.Append(obj?.ToString() ?? GetNullSymbol());
        return Result.FromSuccess();

M Packets/NosSmooth.PacketSerializer/Converters/Basic/BoolStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/BoolStringConverter.cs +1 -1
@@ 16,7 16,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
public class BoolStringConverter : BasicTypeConverter<bool>
{
    /// <inheritdoc />
    public override Result Serialize(bool obj, PacketStringBuilder builder)
    public override Result Serialize(bool obj, ref PacketStringBuilder builder)
    {
        builder.Append(obj ? '1' : '0');
        return Result.FromSuccess();

M Packets/NosSmooth.PacketSerializer/Converters/Basic/ByteStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/ByteStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="byte"/>.
/// </summary>
public class ByteStringConverter : BasicTypeConverter<byte>
public class ByteStringConverter : SpanFormattableTypeConverter<byte>
{
    /// <inheritdoc />
    protected override Result<byte> Deserialize(ReadOnlySpan<char> value)

M Packets/NosSmooth.PacketSerializer/Converters/Basic/IntStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/IntStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="int"/>.
/// </summary>
public class IntStringConverter : BasicTypeConverter<int>
public class IntStringConverter : SpanFormattableTypeConverter<int>
{
    /// <inheritdoc />
    protected override Result<int> Deserialize(ReadOnlySpan<char> value)

M Packets/NosSmooth.PacketSerializer/Converters/Basic/LongStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/LongStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="long"/>.
/// </summary>
public class LongStringConverter : BasicTypeConverter<long>
public class LongStringConverter : SpanFormattableTypeConverter<long>
{
    /// <inheritdoc />
    protected override Result<long> Deserialize(ReadOnlySpan<char> value)

M Packets/NosSmooth.PacketSerializer/Converters/Basic/ShortStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/ShortStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="short"/>.
/// </summary>
public class ShortStringConverter : BasicTypeConverter<short>
public class ShortStringConverter : SpanFormattableTypeConverter<short>
{
    /// <inheritdoc />
    protected override Result<short> Deserialize(ReadOnlySpan<char> value)

A Packets/NosSmooth.PacketSerializer/Converters/Basic/SpanFormattableTypeConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/SpanFormattableTypeConverter.cs +34 -0
@@ 0,0 1,34 @@
//
//  SpanFormattableTypeConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using NosSmooth.PacketSerializer.Abstractions;
using Remora.Results;

namespace NosSmooth.PacketSerializer.Converters.Basic;

/// <summary>
/// A converter for <see cref="ISpanFormattable"/> types such as int, long etc..
/// </summary>
/// <typeparam name="T">The span formattable type.</typeparam>
public abstract class SpanFormattableTypeConverter<T> : BasicTypeConverter<T>
    where T : ISpanFormattable
{
    /// <inheritdoc />
    public override Result Serialize(T? obj, ref PacketStringBuilder builder)
    {
        if (obj is not null)
        {
            builder.Append(obj);
        }
        else
        {
            builder.Append(GetNullSymbol());
        }

        return Result.FromSuccess();
    }
}
\ No newline at end of file

M Packets/NosSmooth.PacketSerializer/Converters/Basic/UIntStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/UIntStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="uint"/>.
/// </summary>
public class UIntStringConverter : BasicTypeConverter<uint>
public class UIntStringConverter : SpanFormattableTypeConverter<uint>
{
    /// <inheritdoc />
    protected override Result<uint> Deserialize(ReadOnlySpan<char> value)

M Packets/NosSmooth.PacketSerializer/Converters/Basic/ULongStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/ULongStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="ulong"/>.
/// </summary>
public class ULongStringConverter : BasicTypeConverter<ulong>
public class ULongStringConverter : SpanFormattableTypeConverter<ulong>
{
    /// <inheritdoc />
    protected override Result<ulong> Deserialize(ReadOnlySpan<char> value)

M Packets/NosSmooth.PacketSerializer/Converters/Basic/UShortStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Basic/UShortStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.PacketSerializer.Converters.Basic;
/// <summary>
/// Converter of <see cref="ushort"/>.
/// </summary>
public class UShortStringConverter : BasicTypeConverter<ushort>
public class UShortStringConverter : SpanFormattableTypeConverter<ushort>
{
    /// <inheritdoc />
    protected override Result<ushort> Deserialize(ReadOnlySpan<char> value)

M Packets/NosSmooth.PacketSerializer/Converters/Common/NameStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/NameStringConverter.cs +1 -1
@@ 16,7 16,7 @@ namespace NosSmooth.PacketSerializer.Converters.Common;
public class NameStringConverter : BaseStringConverter<NameString>
{
    /// <inheritdoc />
    public override Result Serialize(NameString? obj, PacketStringBuilder builder)
    public override Result Serialize(NameString? obj, ref PacketStringBuilder builder)
    {
        if (obj is null)
        {

M Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverter.cs +2 -2
@@ 28,7 28,7 @@ public class NullableWrapperConverter<T> : BaseStringConverter<NullableWrapper<T
    }

    /// <inheritdoc />
    public override Result Serialize(NullableWrapper<T> obj, PacketStringBuilder builder)
    public override Result Serialize(NullableWrapper<T> obj, ref PacketStringBuilder builder)
    {
        if (obj.Value is null)
        {


@@ 42,7 42,7 @@ public class NullableWrapperConverter<T> : BaseStringConverter<NullableWrapper<T
                return Result.FromError(converterResult);
            }

            return converter.Serialize(obj.Value, builder);
            return converter.Serialize(obj.Value, ref builder);
        }

        return Result.FromSuccess();

M Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverter.cs +2 -2
@@ 28,7 28,7 @@ public class OptionalWrapperConverter<T> : BaseStringConverter<OptionalWrapper<T
    }

    /// <inheritdoc />
    public override Result Serialize(OptionalWrapper<T> obj, PacketStringBuilder builder)
    public override Result Serialize(OptionalWrapper<T> obj, ref PacketStringBuilder builder)
    {
        if (obj.Value is null)
        {


@@ 41,7 41,7 @@ public class OptionalWrapperConverter<T> : BaseStringConverter<OptionalWrapper<T
            return Result.FromError(converterResult);
        }

        return converter.Serialize(obj.Value, builder);
        return converter.Serialize(obj.Value, ref builder);

    }


M Packets/NosSmooth.PacketSerializer/Converters/Packets/UpgradeRareSubPacketConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Packets/UpgradeRareSubPacketConverter.cs +1 -1
@@ 18,7 18,7 @@ namespace NosSmooth.PacketSerializer.Converters.Packets;
public class UpgradeRareSubPacketConverter : BaseStringConverter<UpgradeRareSubPacket>
{
    /// <inheritdoc />
    public override Result Serialize(UpgradeRareSubPacket? obj, PacketStringBuilder builder)
    public override Result Serialize(UpgradeRareSubPacket? obj, ref PacketStringBuilder builder)
    {
        if (obj is null)
        {

M Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/EnumStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/EnumStringConverter.cs +1 -1
@@ 28,7 28,7 @@ public class EnumStringConverter<TEnum, TUnderlyingType> : BaseStringConverter<T
    }

    /// <inheritdoc />
    public override Result Serialize(TEnum? obj, PacketStringBuilder builder)
    public override Result Serialize(TEnum? obj, ref PacketStringBuilder builder)
    {
        builder.Append(((TUnderlyingType?)(object?)obj)?.ToString() ?? "-");
        return Result.FromSuccess();

M Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/ListStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/ListStringConverter.cs +2 -2
@@ 29,7 29,7 @@ public class ListStringConverter<TGeneric> : BaseStringConverter<IReadOnlyList<T
    }

    /// <inheritdoc />
    public override Result Serialize(IReadOnlyList<TGeneric>? obj, PacketStringBuilder builder)
    public override Result Serialize(IReadOnlyList<TGeneric>? obj, ref PacketStringBuilder builder)
    {
        if (obj is null)
        {


@@ 44,7 44,7 @@ public class ListStringConverter<TGeneric> : BaseStringConverter<IReadOnlyList<T
                return new ArgumentInvalidError(nameof(builder), "The string builder has to have a prepared level for all lists.");
            }

            var serializeResult = _serializer.Serialize(item, builder);
            var serializeResult = _serializer.Serialize(item, ref builder);
            builder.PopLevel();
            if (!serializeResult.IsSuccess)
            {

M Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/NullableStringConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Special/Converters/NullableStringConverter.cs +2 -2
@@ 31,7 31,7 @@ public class NullableStringConverter<T> : BaseStringConverter<Nullable<T>>
    }

    /// <inheritdoc />
    public override Result Serialize(T? obj, PacketStringBuilder builder)
    public override Result Serialize(T? obj, ref PacketStringBuilder builder)
    {
        if (obj is null)
        {


@@ 39,7 39,7 @@ public class NullableStringConverter<T> : BaseStringConverter<Nullable<T>>
            return Result.FromSuccess();
        }

        return _stringSerializer.Serialize<T>(obj.Value, builder);
        return _stringSerializer.Serialize<T>(obj.Value, ref builder);
    }

    /// <inheritdoc />

M Packets/NosSmooth.PacketSerializer/Converters/Special/StringSerializer.cs => Packets/NosSmooth.PacketSerializer/Converters/Special/StringSerializer.cs +4 -4
@@ 43,7 43,7 @@ public class StringSerializer : IStringSerializer
    }

    /// <inheritdoc />
    public Result Serialize(Type parseType, object? obj, PacketStringBuilder builder)
    public Result Serialize(Type parseType, object? obj, ref PacketStringBuilder builder)
    {
        var converterResult = _converterRepository.GetTypeConverter(parseType);
        if (!converterResult.IsSuccess)


@@ 51,7 51,7 @@ public class StringSerializer : IStringSerializer
            return Result.FromError(converterResult);
        }

        return converterResult.Entity.Serialize(obj, builder);
        return converterResult.Entity.Serialize(obj, ref builder);
    }

    /// <inheritdoc />


@@ 67,7 67,7 @@ public class StringSerializer : IStringSerializer
    }

    /// <inheritdoc />
    public Result Serialize<TParseType>(TParseType? obj, PacketStringBuilder builder)
    public Result Serialize<TParseType>(TParseType? obj, ref PacketStringBuilder builder)
    {
        var converterResult = _converterRepository.GetTypeConverter<TParseType>();
        if (!converterResult.IsSuccess)


@@ 75,6 75,6 @@ public class StringSerializer : IStringSerializer
            return Result.FromError(converterResult);
        }

        return converterResult.Entity.Serialize(obj, builder);
        return converterResult.Entity.Serialize(obj, ref builder);
    }
}
\ No newline at end of file

M Packets/NosSmooth.PacketSerializer/NosSmooth.PacketSerializer.csproj => Packets/NosSmooth.PacketSerializer/NosSmooth.PacketSerializer.csproj +1 -1
@@ 4,10 4,10 @@
        <LangVersion>10</LangVersion>
        <AssemblyName>NosSmooth.PacketSerializer</AssemblyName>
        <RootNamespace>NosSmooth.PacketSerializer</RootNamespace>
        <TargetFrameworks>net7.0;netstandard2.1</TargetFrameworks>
        <Description>NosSmooth's packet string serializer implementation.</Description>
        <VersionPrefix>2.2.7</VersionPrefix>
        <PackageReleaseNotes>Make IPacketSerializer work with ReadOnlySpan.</PackageReleaseNotes>
        <TargetFramework>net7.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>

M Packets/NosSmooth.PacketSerializer/PacketSerializer.cs => Packets/NosSmooth.PacketSerializer/PacketSerializer.cs +5 -3
@@ 34,7 34,7 @@ public class PacketSerializer : IPacketSerializer
    /// <inheritdoc/>
    public Result<string> Serialize(IPacket obj)
    {
        var stringBuilder = new PacketStringBuilder();
        var stringBuilder = new PacketStringBuilder(stackalloc char[500]);
        var infoResult = _packetTypesRepository.FindPacketInfo(obj.GetType());
        if (!infoResult.IsSuccess)
        {


@@ 48,13 48,15 @@ public class PacketSerializer : IPacketSerializer
        }

        stringBuilder.Append(info.Header);
        var serializeResult = info.PacketConverter.Serialize(obj, stringBuilder);
        var serializeResult = info.PacketConverter.Serialize(obj, ref stringBuilder);
        if (!serializeResult.IsSuccess)
        {
            return Result<string>.FromError(serializeResult);
        }

        return stringBuilder.ToString();
        var output = stringBuilder.ToString();
        stringBuilder.Dispose();
        return output;
    }

    /// <inheritdoc/>

M Packets/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/FallbackInlineConverterGenerator.cs => Packets/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/FallbackInlineConverterGenerator.cs +1 -1
@@ 32,7 32,7 @@ public class FallbackInlineConverterGenerator : IInlineConverterGenerator
        var resultName = $"{variableName.Replace(".", string.Empty)}Result";
        textWriter.WriteLine
        (
            $"var {resultName} = _stringSerializer.Serialize<{(typeSyntax?.ToString() ?? typeSymbol!.ToString()).TrimEnd('?')}?>({variableName}, builder);"
            $"var {resultName} = _stringSerializer.Serialize<{(typeSyntax?.ToString() ?? typeSymbol!.ToString()).TrimEnd('?')}?>({variableName}, ref builder);"
        );
        textWriter.WriteLine($"if (!{resultName}.IsSuccess)");
        textWriter.WriteLine("{");

M Packets/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs => Packets/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs +1 -1
@@ 75,7 75,7 @@ public {_packetInfo.Name}Converter(IStringSerializer stringSerializer)
}}

/// <inheritdoc />
public override Result Serialize({_packetInfo.Name}? obj, PacketStringBuilder builder)
public override Result Serialize({_packetInfo.Name}? obj, ref PacketStringBuilder builder)
{{
    if (obj is null)
    {{

M Packets/NosSmooth.Packets/NosSmooth.Packets.csproj => Packets/NosSmooth.Packets/NosSmooth.Packets.csproj +1 -1
@@ 6,8 6,8 @@
        <LangVersion>10</LangVersion>
        <Description>Contains default NosTale packets.</Description>
        <VersionPrefix>3.6.0</VersionPrefix>
        <TargetFrameworks>net7.0;netstandard2.1</TargetFrameworks>
        <PackageReleaseNotes>Add missing ptctl fields.</PackageReleaseNotes>
        <TargetFramework>net7.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>

M Tests/NosSmooth.Packets.Tests/Converters/Basic/BoolStringConverterTests.cs => Tests/NosSmooth.Packets.Tests/Converters/Basic/BoolStringConverterTests.cs +2 -2
@@ 38,8 38,8 @@ public class BoolStringConverterTests
    public void TestsTreatsNullAsMinusOne()
    {
        bool? test = null;
        var stringBuilder = new PacketStringBuilder();
        var serializeResult = _stringSerializer.Serialize(test, stringBuilder);
        var stringBuilder = new PacketStringBuilder(stackalloc char[500]);
        var serializeResult = _stringSerializer.Serialize(test, ref stringBuilder);
        Assert.True(serializeResult.IsSuccess, !serializeResult.IsSuccess ? serializeResult.Error.Message : string.Empty);
        Assert.Equal("-1", stringBuilder.ToString());
    }

M Tests/NosSmooth.Packets.Tests/Converters/Basic/StringConverterTests.cs => Tests/NosSmooth.Packets.Tests/Converters/Basic/StringConverterTests.cs +2 -2
@@ 38,8 38,8 @@ public class StringConverterTests
    public void TestsTreatsNullAsMinus()
    {
        string? test = null;
        var stringBuilder = new PacketStringBuilder();
        var serializeResult = _stringSerializer.Serialize(test, stringBuilder);
        var stringBuilder = new PacketStringBuilder(stackalloc char[500]);
        var serializeResult = _stringSerializer.Serialize(test, ref stringBuilder);
        Assert.True(serializeResult.IsSuccess, !serializeResult.IsSuccess ? serializeResult.Error.Message : string.Empty);
        Assert.Equal("-", stringBuilder.ToString());
    }

M Tests/NosSmooth.Packets.Tests/PacketStringBuilderTests.cs => Tests/NosSmooth.Packets.Tests/PacketStringBuilderTests.cs +2 -2
@@ 21,7 21,7 @@ public class PacketStringBuilderTests
    public void BuilderCorrectlyBuildsComplexArray()
    {
        // in 1 11.12.13|14.15.16|17.18.19
        var stringBuilder = new PacketStringBuilder();
        var stringBuilder = new PacketStringBuilder(stackalloc char[500]);
        stringBuilder.Append("in");
        stringBuilder.Append("1");



@@ 48,7 48,7 @@ public class PacketStringBuilderTests
    [Fact]
    public void BuilderCorrectlyUsesOnceSeparator()
    {
        var stringBuilder = new PacketStringBuilder();
        var stringBuilder = new PacketStringBuilder(stackalloc char[500]);
        stringBuilder.Append("in");

        stringBuilder.SetAfterSeparatorOnce('.');

Do not follow this link