< 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: 136
Uncovered lines: 0
Coverable lines: 136
Total lines: 318
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(...)100%44100%
TryReadLinkId(...)100%44100%
LoadDeferredLink(...)100%44100%
LoadImmediateLink(...)75%44100%
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;
 6using System.Diagnostics.CodeAnalysis;
 7
 8namespace Chronicler;
 9
 10/// <summary>
 11/// Serializes <see cref="IRecordable"/> state graphs to and from MemoryPack through the chronicler API.
 12/// </summary>
 13public static class MemoryPackRecordSerializer
 14{
 115    private static readonly byte[] EmptyRecordBytes = MemoryPackSerializer.Serialize(new MemoryPackRecordEnvelope());
 16
 17    /// <summary>
 18    /// Serializes the current state of a recordable instance into MemoryPack bytes.
 19    /// </summary>
 20    public static byte[] Serialize(IRecordable target)
 721        => Serialize(target, context: null);
 22
 23    /// <summary>
 24    /// Serializes the current state of a recordable instance into MemoryPack bytes.
 25    /// </summary>
 26    public static byte[] Serialize(IRecordable target, ChronicleContext? context)
 27    {
 3128        if (target == null)
 129            throw new ArgumentNullException(nameof(target));
 30
 3031        context ??= new ChronicleContext();
 32
 3033        var chronicler = new MemoryPackRecordWriter(context);
 3034        target.RecordData(chronicler);
 2935        return chronicler.ToArray();
 36    }
 37
 38    /// <summary>
 39    /// Loads MemoryPack state into an existing recordable instance.
 40    /// </summary>
 41    public static void Populate(IRecordable target, byte[] data)
 542        => Populate(target, data, context: null);
 43
 44    /// <summary>
 45    /// Loads MemoryPack state into an existing recordable instance.
 46    /// </summary>
 47    public static void Populate(IRecordable target, byte[] data, ChronicleContext? context)
 48    {
 2749        if (data == null)
 150            throw new ArgumentNullException(nameof(data));
 51
 2652        Populate(target, data.AsSpan(), context);
 2053    }
 54
 55    /// <summary>
 56    /// Loads MemoryPack state into an existing recordable instance.
 57    /// </summary>
 58    public static void Populate(IRecordable target, ReadOnlySpan<byte> data)
 259        => Populate(target, data, context: null);
 60
 61    /// <summary>
 62    /// Loads MemoryPack state into an existing recordable instance.
 63    /// </summary>
 64    public static void Populate(IRecordable target, ReadOnlySpan<byte> data, ChronicleContext? context)
 65    {
 2866        if (target == null)
 167            throw new ArgumentNullException(nameof(target));
 2768        if (data.IsEmpty)
 169            throw new ArgumentException("Serialized bytes must not be empty.", nameof(data));
 70
 2671        context ??= new ChronicleContext();
 72
 2673        var chronicler = new MemoryPackRecordReader(data, context);
 2674        target.RecordData(chronicler);
 2375        context.ResolveDeferredLinks();
 2176    }
 77
 78    private sealed class MemoryPackRecordWriter : IChronicler
 79    {
 4980        private readonly OrderedStringMap<byte[]?> _entries = new(8, StringComparer.Ordinal);
 81
 4982        public MemoryPackRecordWriter(ChronicleContext context)
 83        {
 4984            Context = context ?? throw new ArgumentNullException(nameof(context));
 4985        }
 86
 87        public ChronicleContext Context { get; }
 88
 1789        public SerializationMode Mode => SerializationMode.Saving;
 90
 91        public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 92        {
 5093            if (value is null || EqualityComparer<T>.Default.Equals(value, defaultValue!))
 1094                return;
 95
 4096            _entries[name] = MemoryPackSerializer.Serialize(value);
 4097        }
 98
 99        public void LookDeep<T>(ref T value, string name) where T : class, IRecordable
 100        {
 12101            if (value == null)
 102            {
 1103                _entries[name] = null;
 1104                return;
 105            }
 106
 11107            var nested = new MemoryPackRecordWriter(Context);
 11108            value.RecordData(nested);
 11109            _entries[name] = nested.ToArray();
 11110        }
 111
 112        public void LookDeepStruct<T>(ref T value, string name) where T : struct, IRecordable
 113        {
 5114            var nested = new MemoryPackRecordWriter(Context);
 5115            value.RecordData(nested);
 5116            _entries[name] = nested.ToArray();
 5117        }
 118
 119        public void LookNullableDeep<T>(ref T? value, string name) where T : struct, IRecordable
 120        {
 5121            if (!value.HasValue)
 2122                return;
 123
 3124            T nestedValue = value.Value;
 3125            var nested = new MemoryPackRecordWriter(Context);
 3126            nestedValue.RecordData(nested);
 3127            _entries[name] = nested.ToArray();
 3128        }
 129
 130        public void LookLink<T>(
 131            ref T value,
 132            string name,
 133            string? slot = null,
 134            RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 135            Action<T>? assignLoadedValue = null)
 136        {
 12137            string? id = null;
 12138            if (value is not null
 12139                && !Context.Links.TryGetReferenceId(value, out id, slot))
 140            {
 1141                throw new InvalidOperationException(
 1142                    $"Unable to save link '{name}' of type {typeof(T).Name} because no stable id could be produced{Forma
 143            }
 144
 11145            _entries[name] = MemoryPackSerializer.Serialize(id);
 11146        }
 147
 148        public byte[] ToArray()
 149        {
 48150            return MemoryPackSerializer.Serialize(MemoryPackRecordEnvelope.FromEntries(_entries));
 151        }
 152    }
 153
 154    private sealed class MemoryPackRecordReader : IChronicler
 155    {
 156        private readonly OrderedStringMap<byte[]?> _entries;
 157
 47158        public MemoryPackRecordReader(ReadOnlySpan<byte> data, ChronicleContext context)
 159        {
 47160            MemoryPackRecordEnvelope? envelope = MemoryPackSerializer.Deserialize<MemoryPackRecordEnvelope>(data);
 47161            _entries = envelope?.ToEntryMap() ?? new OrderedStringMap<byte[]?>(8, StringComparer.Ordinal);
 47162            Context = context ?? throw new ArgumentNullException(nameof(context));
 47163        }
 164
 165        public ChronicleContext Context { get; }
 166
 13167        public SerializationMode Mode => SerializationMode.Loading;
 168
 169        public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 170        {
 50171            if (!_entries.TryGetValue(name, out byte[]? entry)
 50172                || entry == null)
 173            {
 24174                value = defaultValue!;
 24175                return;
 176            }
 177
 26178            T? loadedValue = MemoryPackSerializer.Deserialize<T>(entry);
 26179            if (loadedValue is null)
 180            {
 1181                value = defaultValue!;
 1182                return;
 183            }
 184
 25185            value = loadedValue;
 25186        }
 187
 188        public void LookDeep<T>(ref T value, string name) where T : class, IRecordable
 189        {
 11190            if (!_entries.TryGetValue(name, out byte[]? entry)
 11191                || entry == null)
 2192                return;
 193
 9194            if (value == null)
 1195                throw new InvalidOperationException(
 1196                    $"Unable to load '{name}' because {typeof(T).Name} must already be instantiated for a deep chronicle
 197
 8198            var nested = new MemoryPackRecordReader(entry, Context);
 8199            value.RecordData(nested);
 8200        }
 201
 202        public void LookDeepStruct<T>(ref T value, string name) where T : struct, IRecordable
 203        {
 5204            value = CreateDefaultDeepStruct<T>();
 205
 5206            if (!_entries.TryGetValue(name, out byte[]? entry)
 5207                || entry == null)
 1208                return;
 209
 4210            var nested = new MemoryPackRecordReader(entry, Context);
 4211            value.RecordData(nested);
 4212        }
 213
 214        public void LookNullableDeep<T>(ref T? value, string name) where T : struct, IRecordable
 215        {
 5216            if (!_entries.TryGetValue(name, out byte[]? entry)
 5217                || entry == null)
 218            {
 3219                value = null;
 3220                return;
 221            }
 222
 2223            T nestedValue = CreateDefaultDeepStruct<T>();
 2224            var nested = new MemoryPackRecordReader(entry, Context);
 2225            nestedValue.RecordData(nested);
 2226            value = nestedValue;
 2227        }
 228
 229        public void LookLink<T>(
 230            ref T value,
 231            string name,
 232            string? slot = null,
 233            RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 234            Action<T>? assignLoadedValue = null)
 235        {
 10236            if (!TryReadLinkId(name, out string? id))
 237            {
 2238                value = default!;
 2239                return;
 240            }
 241
 8242            if (resolveMode == RecordLinkResolveMode.Deferred)
 243            {
 5244                LoadDeferredLink(ref value, name, id, slot, assignLoadedValue);
 4245                return;
 246            }
 247
 3248            LoadImmediateLink(ref value, name, id, slot, assignLoadedValue);
 2249        }
 250
 251        private bool TryReadLinkId(string name, [NotNullWhen(true)] out string? id)
 252        {
 10253            if (!_entries.TryGetValue(name, out byte[]? entry)
 10254                || entry == null)
 255            {
 1256                id = null;
 1257                return false;
 258            }
 259
 9260            id = MemoryPackSerializer.Deserialize<string>(entry);
 9261            return id != null;
 262        }
 263
 264        private void LoadDeferredLink<T>(
 265            ref T value,
 266            string name,
 267            string id,
 268            string? slot,
 269            Action<T>? assignLoadedValue)
 270        {
 5271            if (assignLoadedValue == null)
 1272                throw new InvalidOperationException(
 1273                    $"Deferred link '{name}' of type {typeof(T).Name} requires an assignment callback.");
 274
 4275            if (Context.Links.TryResolve(id, out T? deferredValue, slot))
 276            {
 1277                value = deferredValue!;
 1278                assignLoadedValue(deferredValue!);
 1279                return;
 280            }
 281
 3282            Context.QueueDeferredLink(name, id, slot, assignLoadedValue);
 3283            value = default!;
 3284        }
 285
 286        private void LoadImmediateLink<T>(
 287            ref T value,
 288            string name,
 289            string id,
 290            string? slot,
 291            Action<T>? assignLoadedValue)
 292        {
 3293            if (!Context.Links.TryResolve(id, out T? resolvedValue, slot))
 1294                throw new InvalidOperationException(
 1295                    $"Unable to load link '{name}' of type {typeof(T).Name} with id '{id}'{FormatSlot(slot)}.");
 296
 2297            value = resolvedValue!;
 2298            assignLoadedValue?.Invoke(resolvedValue!);
 2299        }
 300
 301        private T CreateDefaultDeepStruct<T>() where T : struct, IRecordable
 302        {
 7303            T defaultValue = new();
 7304            var nested = new MemoryPackRecordReader(EmptyRecordBytes, Context);
 7305            defaultValue.RecordData(nested);
 7306            return defaultValue;
 307        }
 308    }
 309
 310    private static string FormatSlot(string? slot)
 311    {
 2312        return string.IsNullOrEmpty(slot)
 2313            ? string.Empty
 2314            : $" in slot '{slot}'";
 315    }
 316}
 317
 318#endif