< Summary

Information
Class: Chronicler.MemoryPackRecordSerializer
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/MemoryPack/MemoryPackRecordSerializer.cs
Line coverage
100%
Covered lines: 131
Uncovered lines: 0
Coverable lines: 131
Total lines: 294
Line coverage: 100%
Branch coverage
91%
Covered branches: 64
Total branches: 70
Branch coverage: 91.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Serialize(...)100%11100%
Serialize(...)100%44100%
Populate(...)100%11100%
Populate(...)100%22100%
Populate(...)100%11100%
Populate(...)100%66100%
.ctor(...)50%22100%
get_Mode()100%11100%
LookValue(...)100%44100%
LookDeep(...)100%22100%
LookDeepStruct(...)100%11100%
LookNullableDeep(...)100%22100%
LookLink(...)100%44100%
ToArray()100%11100%
.ctor(...)50%66100%
get_Mode()100%11100%
LookValue(...)100%66100%
LookDeep(...)100%66100%
LookDeepStruct(...)100%44100%
LookNullableDeep(...)100%44100%
LookLink(...)93.75%1616100%
CreateDefaultDeepStruct()100%11100%
FormatSlot(...)50%22100%

File(s)

/home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/MemoryPack/MemoryPackRecordSerializer.cs

#LineLine coverage
 1#if !CHRONICLER_DISABLE_MEMORYPACK
 2
 3using MemoryPack;
 4using System;
 5using System.Collections.Generic;
 6
 7namespace Chronicler;
 8
 9/// <summary>
 10/// Serializes <see cref="IRecordable"/> state graphs to and from MemoryPack through the chronicler API.
 11/// </summary>
 12public static class MemoryPackRecordSerializer
 13{
 114    private static readonly byte[] EmptyRecordBytes = MemoryPackSerializer.Serialize(new MemoryPackRecordEnvelope());
 15
 16    /// <summary>
 17    /// Serializes the current state of a recordable instance into MemoryPack bytes.
 18    /// </summary>
 19    public static byte[] Serialize(IRecordable target)
 620        => Serialize(target, context: null);
 21
 22    /// <summary>
 23    /// Serializes the current state of a recordable instance into MemoryPack bytes.
 24    /// </summary>
 25    public static byte[] Serialize(IRecordable target, ChronicleContext? context)
 26    {
 2927        if (target == null)
 128            throw new ArgumentNullException(nameof(target));
 29
 2830        context ??= new ChronicleContext();
 31
 2832        var chronicler = new MemoryPackRecordWriter(context);
 2833        target.RecordData(chronicler);
 2734        return chronicler.ToArray();
 35    }
 36
 37    /// <summary>
 38    /// Loads MemoryPack state into an existing recordable instance.
 39    /// </summary>
 40    public static void Populate(IRecordable target, byte[] data)
 441        => Populate(target, data, context: null);
 42
 43    /// <summary>
 44    /// Loads MemoryPack state into an existing recordable instance.
 45    /// </summary>
 46    public static void Populate(IRecordable target, byte[] data, ChronicleContext? context)
 47    {
 2548        if (data == null)
 149            throw new ArgumentNullException(nameof(data));
 50
 2451        Populate(target, data.AsSpan(), context);
 1852    }
 53
 54    /// <summary>
 55    /// Loads MemoryPack state into an existing recordable instance.
 56    /// </summary>
 57    public static void Populate(IRecordable target, ReadOnlySpan<byte> data)
 258        => Populate(target, data, context: null);
 59
 60    /// <summary>
 61    /// Loads MemoryPack state into an existing recordable instance.
 62    /// </summary>
 63    public static void Populate(IRecordable target, ReadOnlySpan<byte> data, ChronicleContext? context)
 64    {
 2665        if (target == null)
 166            throw new ArgumentNullException(nameof(target));
 2567        if (data.IsEmpty)
 168            throw new ArgumentException("Serialized bytes must not be empty.", nameof(data));
 69
 2470        context ??= new ChronicleContext();
 71
 2472        var chronicler = new MemoryPackRecordReader(data, context);
 2473        target.RecordData(chronicler);
 2174        context.ResolveDeferredLinks();
 1975    }
 76
 77    private sealed class MemoryPackRecordWriter : IChronicler
 78    {
 4379        private readonly OrderedStringMap<byte[]?> _entries = new(8, StringComparer.Ordinal);
 80
 4381        public MemoryPackRecordWriter(ChronicleContext context)
 82        {
 4383            Context = context ?? throw new ArgumentNullException(nameof(context));
 4384        }
 85
 86        public ChronicleContext Context { get; }
 87
 1688        public SerializationMode Mode => SerializationMode.Saving;
 89
 90        public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 91        {
 4392            if (value is null || EqualityComparer<T>.Default.Equals(value, defaultValue!))
 993                return;
 94
 3495            _entries[name] = MemoryPackSerializer.Serialize(value);
 3496        }
 97
 98        public void LookDeep<T>(ref T value, string name) where T : class, IRecordable
 99        {
 10100            if (value == null)
 101            {
 1102                _entries[name] = null;
 1103                return;
 104            }
 105
 9106            var nested = new MemoryPackRecordWriter(Context);
 9107            value.RecordData(nested);
 9108            _entries[name] = nested.ToArray();
 9109        }
 110
 111        public void LookDeepStruct<T>(ref T value, string name) where T : struct, IRecordable
 112        {
 4113            var nested = new MemoryPackRecordWriter(Context);
 4114            value.RecordData(nested);
 4115            _entries[name] = nested.ToArray();
 4116        }
 117
 118        public void LookNullableDeep<T>(ref T? value, string name) where T : struct, IRecordable
 119        {
 4120            if (!value.HasValue)
 2121                return;
 122
 2123            T nestedValue = value.Value;
 2124            var nested = new MemoryPackRecordWriter(Context);
 2125            nestedValue.RecordData(nested);
 2126            _entries[name] = nested.ToArray();
 2127        }
 128
 129        public void LookLink<T>(
 130            ref T value,
 131            string name,
 132            string? slot = null,
 133            RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 134            Action<T>? assignLoadedValue = null)
 135        {
 12136            string? id = null;
 12137            if (value is not null
 12138                && !Context.Links.TryGetReferenceId(value, out id, slot))
 139            {
 1140                throw new InvalidOperationException(
 1141                    $"Unable to save link '{name}' of type {typeof(T).Name} because no stable id could be produced{Forma
 142            }
 143
 11144            _entries[name] = MemoryPackSerializer.Serialize(id);
 11145        }
 146
 147        public byte[] ToArray()
 148        {
 42149            return MemoryPackSerializer.Serialize(MemoryPackRecordEnvelope.FromEntries(_entries));
 150        }
 151    }
 152
 153    private sealed class MemoryPackRecordReader : IChronicler
 154    {
 155        private readonly OrderedStringMap<byte[]?> _entries;
 156
 39157        public MemoryPackRecordReader(ReadOnlySpan<byte> data, ChronicleContext context)
 158        {
 39159            MemoryPackRecordEnvelope? envelope = MemoryPackSerializer.Deserialize<MemoryPackRecordEnvelope>(data);
 39160            _entries = envelope?.ToEntryMap() ?? new OrderedStringMap<byte[]?>(8, StringComparer.Ordinal);
 39161            Context = context ?? throw new ArgumentNullException(nameof(context));
 39162        }
 163
 164        public ChronicleContext Context { get; }
 165
 12166        public SerializationMode Mode => SerializationMode.Loading;
 167
 168        public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 169        {
 41170            if (!_entries.TryGetValue(name, out byte[]? entry)
 41171                || entry == null)
 172            {
 20173                value = defaultValue!;
 20174                return;
 175            }
 176
 21177            T? loadedValue = MemoryPackSerializer.Deserialize<T>(entry);
 21178            if (loadedValue is null)
 179            {
 1180                value = defaultValue!;
 1181                return;
 182            }
 183
 20184            value = loadedValue;
 20185        }
 186
 187        public void LookDeep<T>(ref T value, string name) where T : class, IRecordable
 188        {
 9189            if (!_entries.TryGetValue(name, out byte[]? entry)
 9190                || entry == null)
 2191                return;
 192
 7193            if (value == null)
 1194                throw new InvalidOperationException(
 1195                    $"Unable to load '{name}' because {typeof(T).Name} must already be instantiated for a deep chronicle
 196
 6197            var nested = new MemoryPackRecordReader(entry, Context);
 6198            value.RecordData(nested);
 6199        }
 200
 201        public void LookDeepStruct<T>(ref T value, string name) where T : struct, IRecordable
 202        {
 4203            value = CreateDefaultDeepStruct<T>();
 204
 4205            if (!_entries.TryGetValue(name, out byte[]? entry)
 4206                || entry == null)
 1207                return;
 208
 3209            var nested = new MemoryPackRecordReader(entry, Context);
 3210            value.RecordData(nested);
 3211        }
 212
 213        public void LookNullableDeep<T>(ref T? value, string name) where T : struct, IRecordable
 214        {
 4215            if (!_entries.TryGetValue(name, out byte[]? entry)
 4216                || entry == null)
 217            {
 3218                value = null;
 3219                return;
 220            }
 221
 1222            T nestedValue = CreateDefaultDeepStruct<T>();
 1223            var nested = new MemoryPackRecordReader(entry, Context);
 1224            nestedValue.RecordData(nested);
 1225            value = nestedValue;
 1226        }
 227
 228        public void LookLink<T>(
 229            ref T value,
 230            string name,
 231            string? slot = null,
 232            RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 233            Action<T>? assignLoadedValue = null)
 234        {
 10235            if (!_entries.TryGetValue(name, out byte[]? entry)
 10236                || entry == null)
 237            {
 1238                value = default!;
 1239                return;
 240            }
 241
 9242            string? id = MemoryPackSerializer.Deserialize<string>(entry);
 9243            if (id == null)
 244            {
 1245                value = default!;
 1246                return;
 247            }
 248
 8249            if (resolveMode == RecordLinkResolveMode.Deferred)
 250            {
 5251                if (assignLoadedValue == null)
 1252                    throw new InvalidOperationException(
 1253                        $"Deferred link '{name}' of type {typeof(T).Name} requires an assignment callback.");
 254
 4255                if (Context.Links.TryResolve(id, out T? deferredValue, slot))
 256                {
 1257                    value = deferredValue!;
 1258                    assignLoadedValue(deferredValue!);
 1259                    return;
 260                }
 261
 3262                Context.QueueDeferredLink(name, id, slot, assignLoadedValue);
 3263                value = default!;
 3264                return;
 265            }
 266
 3267            if (!Context.Links.TryResolve(id, out T? resolvedValue, slot))
 268            {
 1269                throw new InvalidOperationException(
 1270                    $"Unable to load link '{name}' of type {typeof(T).Name} with id '{id}'{FormatSlot(slot)}.");
 271            }
 272
 2273            value = resolvedValue!;
 2274            assignLoadedValue?.Invoke(resolvedValue!);
 2275        }
 276
 277        private T CreateDefaultDeepStruct<T>() where T : struct, IRecordable
 278        {
 5279            T defaultValue = new();
 5280            var nested = new MemoryPackRecordReader(EmptyRecordBytes, Context);
 5281            defaultValue.RecordData(nested);
 5282            return defaultValue;
 283        }
 284    }
 285
 286    private static string FormatSlot(string? slot)
 287    {
 2288        return string.IsNullOrEmpty(slot)
 2289            ? string.Empty
 2290            : $" in slot '{slot}'";
 291    }
 292}
 293
 294#endif