~ruther/NosSmooth

551cf777ad2d178af63e93ebffedac9d17d5ca3b — František Boháček 2 years ago ab0f326
feat(packets): add nullable wrapper to represent a compoud type that may be null as a whole
A Packets/NosSmooth.PacketSerializer.Abstractions/NullableWrapper.cs => Packets/NosSmooth.PacketSerializer.Abstractions/NullableWrapper.cs +31 -0
@@ 0,0 1,31 @@
//
//  NullableWrapper.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.Diagnostics.CodeAnalysis;

namespace NosSmooth.PacketSerializer.Abstractions;

/// <summary>
/// Wraps a compound value that may not be present
/// and there will be "-1" instead in the packet.
/// The converter of underlying type will be called
/// if and only if the value is not null.
/// </summary>
/// <param name="Value">The value.</param>
/// <typeparam name="T">The underlying type.</typeparam>
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "Fix this, this should not happen.")]
public record struct NullableWrapper<T>(T? Value)
{
    /// <summary>
    /// Unwrap the underlying value.
    /// </summary>
    /// <param name="wrapper">The wrapper to unwrap.</param>
    /// <returns>The unwrapped value.</returns>
    public static implicit operator T?(NullableWrapper<T> wrapper)
    {
        return wrapper.Value;
    }
}
\ No newline at end of file

A Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverter.cs +79 -0
@@ 0,0 1,79 @@
//
//  NullableWrapperConverter.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.Common;

/// <summary>
/// Converter of <see cref="NullableWrapper{T}"/>.
/// </summary>
/// <typeparam name="T">The underlying type.</typeparam>
public class NullableWrapperConverter<T> : BaseStringConverter<NullableWrapper<T>>
{
    private readonly StringConverterRepository _converterRepository;

    /// <summary>
    /// Initializes a new instance of the <see cref="NullableWrapperConverter{T}"/> class.
    /// </summary>
    /// <param name="converterRepository">The converter repository.</param>
    public NullableWrapperConverter(StringConverterRepository converterRepository)
    {
        _converterRepository = converterRepository;
    }

    /// <inheritdoc />
    public override Result Serialize(NullableWrapper<T> obj, PacketStringBuilder builder)
    {
        if (obj.Value is null)
        {
            builder.Append("-1");
        }
        else
        {
            var converterResult = _converterRepository.GetTypeConverter<T>();
            if (!converterResult.IsDefined(out var converter))
            {
                return Result.FromError(converterResult);
            }

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

        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public override Result<NullableWrapper<T>> Deserialize(ref PacketStringEnumerator stringEnumerator)
    {
        var tokenResult = stringEnumerator.GetNextToken(out var packetToken, false);
        if (!tokenResult.IsSuccess)
        {
            return Result<NullableWrapper<T>>.FromError(tokenResult);
        }

        if (packetToken.Token.Length == 2 && packetToken.Token.StartsWith("-1"))
        {
            return Result<NullableWrapper<T>>.FromSuccess(new NullableWrapper<T>(default));
        }

        var converterResult = _converterRepository.GetTypeConverter<T>();
        if (!converterResult.IsDefined(out var converter))
        {
            return Result<NullableWrapper<T>>.FromError(converterResult);
        }

        var deserializationResult = converter.Deserialize(ref stringEnumerator);
        if (!deserializationResult.IsDefined(out var deserialization))
        {
            return Result<NullableWrapper<T>>.FromError(deserializationResult);
        }

        return new NullableWrapper<T>(deserialization);
    }
}
\ No newline at end of file

A Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverterFactory.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/NullableWrapperConverterFactory.cs +57 -0
@@ 0,0 1,57 @@
//
//  NullableWrapperConverterFactory.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 Microsoft.Extensions.DependencyInjection;
using NosSmooth.PacketSerializer.Abstractions;
using NosSmooth.PacketSerializer.Converters.Special;
using NosSmooth.PacketSerializer.Converters.Special.Converters;
using NosSmooth.PacketSerializer.Extensions;
using Remora.Results;

namespace NosSmooth.PacketSerializer.Converters.Common;

/// <summary>
/// Converts <see cref="NullableWrapper{T}"/>.
/// </summary>
public class NullableWrapperConverterFactory : IStringConverterFactory
{
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// Initializes a new instance of the <see cref="NullableWrapperConverterFactory"/> class.
    /// </summary>
    /// <param name="serviceProvider">The service provider.</param>
    public NullableWrapperConverterFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    /// <inheritdoc />
    public bool ShouldHandle(Type type)
        => type.GetGenericTypeDefinition() == typeof(NullableWrapper<>);

    /// <inheritdoc />
    public Result<IStringConverter> CreateTypeSerializer(Type type)
    {
        var underlyingType = type.GetGenericArguments()[0];
        var serializerType = typeof(NullableWrapperConverter<>).MakeGenericType(underlyingType);

        try
        {
            return Result<IStringConverter>.FromSuccess
                ((IStringConverter)ActivatorUtilities.CreateInstance(_serviceProvider, serializerType));
        }
        catch (Exception e)
        {
            return e;
        }
    }

    /// <inheritdoc />
    public Result<IStringConverter<T>> CreateTypeSerializer<T>()
        => CreateTypeSerializer(typeof(T)).Cast<IStringConverter<T>, IStringConverter>();
}
\ No newline at end of file

M Packets/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs => Packets/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs +6 -0
@@ 130,6 130,12 @@ public class SourceGenerator : ISourceGenerator
                    $"{packetRecord.GetPrefix()}.{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs",
                    stringWriter.GetStringBuilder().ToString()
                );

                File.WriteAllText
                (
                    Path.Combine(Path.GetTempPath(), $"{packetRecord.GetPrefix()}.{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs"),
                    stringWriter.GetStringBuilder().ToString()
                );
            }
        }


M Packets/NosSmooth.Packets/Server/Login/CListPacket.cs => Packets/NosSmooth.Packets/Server/Login/CListPacket.cs +2 -1
@@ 6,6 6,7 @@

using NosSmooth.Packets.Enums.Players;
using NosSmooth.Packets.Server.Maps;
using NosSmooth.PacketSerializer.Abstractions;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Abstractions.Common;



@@ 73,7 74,7 @@ public record CListPacket
    [PacketIndex(14)]
    byte Unknown3,
    [PacketListIndex(15, ListSeparator = '.', InnerSeparator = '.')]
    IReadOnlyList<CListPetSubPacket> PetsSubPacket,
    IReadOnlyList<NullableWrapper<CListPetSubPacket>> PetsSubPacket,
    [PacketIndex(16)]
    byte HatDesign,
    [PacketIndex(17)]

M Packets/NosSmooth.Packets/Server/Mates/ScNEquipmentSubPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNEquipmentSubPacket.cs +4 -4
@@ 21,9 21,9 @@ namespace NosSmooth.Packets.Server.Mates;
public record ScNEquipmentSubPacket
(
    [PacketIndex(0)]
    int? ItemVNum,
    [PacketIndex(1, IsOptional = true)]
    int ItemVNum,
    [PacketIndex(1)]
    sbyte? ItemRare,
    [PacketIndex(2, IsOptional = true)]
    byte? ItemUpgrade
    [PacketIndex(2)]
    byte ItemUpgrade
) : IPacket;
\ No newline at end of file

M Packets/NosSmooth.Packets/Server/Mates/ScNPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNPacket.cs +9 -8
@@ 7,6 7,7 @@
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Server.Character;
using NosSmooth.Packets.Server.Maps;
using NosSmooth.PacketSerializer.Abstractions;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Abstractions.Common;



@@ 73,13 74,13 @@ public record ScNPacket
    [PacketIndex(5)]
    long Experience,
    [PacketIndex(6, InnerSeparator = '.')]
    ScNEquipmentSubPacket? WeaponSubPacket,
    NullableWrapper<ScNEquipmentSubPacket> WeaponSubPacket,
    [PacketIndex(7, InnerSeparator = '.')]
    ScNEquipmentSubPacket? ArmorSubPacket,
    NullableWrapper<ScNEquipmentSubPacket> ArmorSubPacket,
    [PacketIndex(8, InnerSeparator = '.')]
    ScNEquipmentSubPacket? GauntletSubPacket,
    NullableWrapper<ScNEquipmentSubPacket> GauntletSubPacket,
    [PacketIndex(9, InnerSeparator = '.')]
    ScNEquipmentSubPacket? BootsSubPacket,
    NullableWrapper<ScNEquipmentSubPacket> BootsSubPacket,
    [PacketIndex(10, InnerSeparator = '.')]
    short Unknown1,
    [PacketIndex(11)]


@@ 133,11 134,11 @@ public record ScNPacket
    [PacketIndex(35)]
    bool IsSummonable,
    [PacketIndex(36, InnerSeparator = '.')]
    ScNSpSubPacket? SpSubPacket,
    NullableWrapper<ScNSpSubPacket> SpSubPacket,
    [PacketIndex(37, InnerSeparator = '.')]
    ScNSkillSubPacket? Skill1SubPacket,
    NullableWrapper<ScNSkillSubPacket> Skill1SubPacket,
    [PacketIndex(38, InnerSeparator = '.')]
    ScNSkillSubPacket? Skill2SubPacket,
    NullableWrapper<ScNSkillSubPacket> Skill2SubPacket,
    [PacketIndex(39, InnerSeparator = '.')]
    ScNSkillSubPacket? Skill3SubPacket
    NullableWrapper<ScNSkillSubPacket> Skill3SubPacket
) : IPacket;
\ No newline at end of file

M Packets/NosSmooth.Packets/Server/Mates/ScNSkillSubPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNSkillSubPacket.cs +3 -3
@@ 21,7 21,7 @@ namespace NosSmooth.Packets.Server.Mates;
public record ScNSkillSubPacket
(
    [PacketIndex(0)]
    int? SkillVNum,
    [PacketIndex(1, IsOptional = true)]
    PartnerSkillRank? Rank
    int SkillVNum,
    [PacketIndex(1)]
    PartnerSkillRank Rank
) : IPacket;
\ No newline at end of file

M Packets/NosSmooth.Packets/Server/Mates/ScNSpSubPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNSpSubPacket.cs +3 -3
@@ 20,7 20,7 @@ namespace NosSmooth.Packets.Server.Mates;
public record ScNSpSubPacket
(
    [PacketIndex(0)]
    long? ItemVNum,
    [PacketIndex(1, IsOptional = true)]
    byte? AgilityPercentage
    long ItemVNum,
    [PacketIndex(1)]
    byte AgilityPercentage
);
\ No newline at end of file

Do not follow this link