< Summary

Information
Class: Chronicler.ChronicleHashChronicler
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Recording/ChronicleHashChronicler.cs
Line coverage
97%
Covered lines: 170
Uncovered lines: 4
Coverable lines: 174
Total lines: 422
Line coverage: 97.7%
Branch coverage
90%
Covered branches: 59
Total branches: 65
Branch coverage: 90.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
get_Context()50%2266.66%
get_Mode()100%11100%
Contribute(...)100%11100%
LookValue(...)100%11100%
LookDeep(...)100%22100%
LookDeepStruct(...)100%11100%
LookNullableDeep(...)100%22100%
LookLink(...)100%66100%
Rent()100%44100%
WriteRecordStart(...)100%11100%
WriteRecordEnd()100%11100%
WriteFieldHeader(...)100%22100%
WriteLeafKind(...)100%22100%
GetLeafKind(...)100%44100%
WriteLeafValue(...)86.66%151590.9%
WriteEnumValue(...)100%66100%
GetStableTypeName(...)100%22100%
BuildStableTypeName(...)75%8885.71%
BuildStableArrayTypeName(...)100%22100%
BuildStableGenericTypeName(...)83.33%66100%
FormatSlot(...)100%22100%

File(s)

/home/runner/work/Chronicler/Chronicler/src/Chronicler/Recording/ChronicleHashChronicler.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Runtime.CompilerServices;
 4using System.Text;
 5
 6namespace Chronicler;
 7
 8internal sealed class ChronicleHashChronicler : IChronicler
 9{
 10    private enum FieldKind : byte
 11    {
 12        Value = 1,
 13        DeepClass = 2,
 14        DeepStruct = 3,
 15        NullableDeepStruct = 4,
 16        Link = 5
 17    }
 18
 19    private enum LeafKind : byte
 20    {
 21        Bool = 1,
 22        Byte = 2,
 23        SByte = 3,
 24        Int16 = 4,
 25        UInt16 = 5,
 26        Int32 = 6,
 27        UInt32 = 7,
 28        Int64 = 8,
 29        UInt64 = 9,
 30        Char = 10,
 31        String = 11,
 32        Enum = 12
 33    }
 34
 35    [ThreadStatic]
 36    private static ChronicleHashChronicler? _cached;
 37
 138    private static readonly Dictionary<Type, LeafKind> LeafKindsByType = new()
 139    {
 140        [typeof(bool)] = LeafKind.Bool,
 141        [typeof(byte)] = LeafKind.Byte,
 142        [typeof(sbyte)] = LeafKind.SByte,
 143        [typeof(short)] = LeafKind.Int16,
 144        [typeof(ushort)] = LeafKind.UInt16,
 145        [typeof(int)] = LeafKind.Int32,
 146        [typeof(uint)] = LeafKind.UInt32,
 147        [typeof(long)] = LeafKind.Int64,
 148        [typeof(ulong)] = LeafKind.UInt64,
 149        [typeof(char)] = LeafKind.Char,
 150        [typeof(string)] = LeafKind.String
 151    };
 52
 153    private static readonly Dictionary<Type, string> StableTypeNames = new();
 154    private static readonly object StableTypeNameSync = new();
 55
 56    private ChronicleHashWriter _writer;
 57    private ChronicleContext? _context;
 58    private bool _isInUse;
 59
 460    private ChronicleHashChronicler()
 61    {
 462    }
 63
 64    public ChronicleContext Context
 65    {
 66        get
 67        {
 568            if (_context == null)
 069                throw new InvalidOperationException("Record hash chronicler context is not active.");
 70
 571            return _context;
 72        }
 73    }
 74
 3687475    public SerializationMode Mode => SerializationMode.Saving;
 76
 77    public static void Contribute(IRecordable target, ChronicleContext context, ref ChronicleHashWriter writer)
 78    {
 7378379        ChronicleHashChronicler chronicler = Rent();
 80
 7378381        chronicler._writer = writer;
 7378382        chronicler._context = context;
 83
 84        try
 85        {
 7378386            chronicler.WriteRecordStart(target.GetType());
 7378387            target.RecordData(chronicler);
 7377988            chronicler.WriteRecordEnd();
 7377989            writer = chronicler._writer;
 7377990        }
 91        finally
 92        {
 7378393            chronicler._writer = default;
 7378394            chronicler._context = null;
 7378395            chronicler._isInUse = false;
 7378396        }
 7377997    }
 98
 99    public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 100    {
 221470101        WriteFieldHeader(name, FieldKind.Value, typeof(T));
 221469102        WriteLeafKind(typeof(T), name);
 221468103        WriteLeafValue(ref value, name);
 104
 221468105        T declaredDefault = defaultValue!;
 221468106        WriteLeafValue(ref declaredDefault, name);
 221468107    }
 108
 109    public void LookDeep<T>(ref T value, string name)
 110        where T : class, IRecordable
 111    {
 36874112        WriteFieldHeader(name, FieldKind.DeepClass, typeof(T));
 113
 36874114        if (value == null)
 115        {
 1116            _writer.WriteBool(false);
 1117            return;
 118        }
 119
 36873120        _writer.WriteBool(true);
 36873121        WriteRecordStart(typeof(T));
 36873122        value.RecordData(this);
 36873123        WriteRecordEnd();
 36873124    }
 125
 126    public void LookDeepStruct<T>(ref T value, string name)
 127        where T : struct, IRecordable
 128    {
 36872129        WriteFieldHeader(name, FieldKind.DeepStruct, typeof(T));
 36872130        WriteRecordStart(typeof(T));
 36872131        value.RecordData(this);
 36872132        WriteRecordEnd();
 36872133    }
 134
 135    public void LookNullableDeep<T>(ref T? value, string name)
 136        where T : struct, IRecordable
 137    {
 36872138        WriteFieldHeader(name, FieldKind.NullableDeepStruct, typeof(T));
 139
 36872140        if (!value.HasValue)
 141        {
 3142            _writer.WriteBool(false);
 3143            return;
 144        }
 145
 36869146        _writer.WriteBool(true);
 36869147        T nestedValue = value.Value;
 36869148        WriteRecordStart(typeof(T));
 36869149        nestedValue.RecordData(this);
 36869150        WriteRecordEnd();
 36869151    }
 152
 153    public void LookLink<T>(
 154        ref T value,
 155        string name,
 156        string? slot = null,
 157        RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 158        Action<T>? assignLoadedValue = null)
 159    {
 6160        string? normalizedSlot = string.IsNullOrEmpty(slot) ? null : slot;
 6161        WriteFieldHeader(name, FieldKind.Link, typeof(T));
 6162        _writer.WriteString(normalizedSlot);
 6163        _writer.WriteEnum(resolveMode);
 164
 6165        if (value is null)
 166        {
 1167            _writer.WriteString(null);
 1168            return;
 169        }
 170
 5171        if (!Context.Links.TryGetReferenceId(value, out string? id, normalizedSlot))
 172        {
 2173            throw new InvalidOperationException(
 2174                $"Unable to hash link '{name}' of type {typeof(T).Name} because no stable id could be produced{FormatSlo
 175        }
 176
 3177        _writer.WriteString(id);
 3178    }
 179
 180    private static ChronicleHashChronicler Rent()
 181    {
 73783182        ChronicleHashChronicler? cached = _cached;
 73783183        if (cached == null)
 184        {
 3185            cached = new ChronicleHashChronicler();
 3186            _cached = cached;
 187        }
 188
 73783189        if (cached._isInUse)
 1190            return new ChronicleHashChronicler { _isInUse = true };
 191
 73782192        cached._isInUse = true;
 73782193        return cached;
 194    }
 195
 196    private void WriteRecordStart(Type declaredType)
 197    {
 184397198        _writer.WriteSection("chronicler.record", 1);
 184397199        _writer.WriteString(GetStableTypeName(declaredType));
 184397200    }
 201
 202    private void WriteRecordEnd()
 203    {
 184393204        _writer.WriteSection("chronicler.record.end", 1);
 184393205    }
 206
 207    private void WriteFieldHeader(string name, FieldKind kind, Type declaredType)
 208    {
 332094209        if (name == null)
 1210            throw new ArgumentNullException(nameof(name));
 211
 332093212        _writer.WriteString(name);
 332093213        _writer.WriteByte((byte)kind);
 332093214        _writer.WriteString(GetStableTypeName(declaredType));
 332093215    }
 216
 217    private void WriteLeafKind(Type declaredType, string name)
 218    {
 221469219        LeafKind kind = GetLeafKind(declaredType, name);
 221468220        _writer.WriteByte((byte)kind);
 221
 221468222        if (kind == LeafKind.Enum)
 36944223            _writer.WriteString(GetStableTypeName(Enum.GetUnderlyingType(declaredType)));
 221468224    }
 225
 226    private static LeafKind GetLeafKind(Type declaredType, string name)
 227    {
 664405228        if (declaredType.IsEnum)
 110832229            return LeafKind.Enum;
 230
 553573231        if (LeafKindsByType.TryGetValue(declaredType, out LeafKind kind))
 553572232            return kind;
 233
 1234        throw new NotSupportedException($"Unsupported record-hash leaf value '{name}' of type {GetStableTypeName(declare
 235    }
 236
 237    private void WriteLeafValue<T>(ref T value, string name)
 238    {
 442936239        Type declaredType = typeof(T);
 442936240        LeafKind kind = GetLeafKind(declaredType, name);
 442936241        if (kind == LeafKind.String)
 242        {
 73776243            _writer.WriteString(value as string);
 73776244            return;
 245        }
 246
 369160247        _writer.WriteBool(true);
 248
 369160249        _ = kind switch
 369160250        {
 147508251            LeafKind.Bool => WriteBool(ref value),
 26252            LeafKind.Byte => WriteByte(ref value),
 26253            LeafKind.SByte => WriteSByte(ref value),
 26254            LeafKind.Int16 => WriteInt16(ref value),
 26255            LeafKind.UInt16 => WriteUInt16(ref value),
 147556256            LeafKind.Int32 => WriteInt32(ref value),
 26257            LeafKind.UInt32 => WriteUInt32(ref value),
 26258            LeafKind.Int64 => WriteInt64(ref value),
 26259            LeafKind.UInt64 => WriteUInt64(ref value),
 26260            LeafKind.Char => WriteChar(ref value),
 73888261            LeafKind.Enum => WriteEnum(ref value),
 0262            _ => throw new NotSupportedException(
 0263                $"Unsupported record-hash leaf value '{name}' of type {GetStableTypeName(declaredType)}.")
 369160264        };
 265
 266        byte WriteBool(ref T leaf)
 267        {
 268            _writer.WriteBool(Unsafe.As<T, bool>(ref leaf));
 269            return 0;
 270        }
 271
 272        byte WriteByte(ref T leaf)
 273        {
 274            _writer.WriteByte(Unsafe.As<T, byte>(ref leaf));
 275            return 0;
 276        }
 277
 278        byte WriteSByte(ref T leaf)
 279        {
 280            _writer.WriteSByte(Unsafe.As<T, sbyte>(ref leaf));
 281            return 0;
 282        }
 283
 284        byte WriteInt16(ref T leaf)
 285        {
 286            _writer.WriteInt16(Unsafe.As<T, short>(ref leaf));
 287            return 0;
 288        }
 289
 290        byte WriteUInt16(ref T leaf)
 291        {
 292            _writer.WriteUInt16(Unsafe.As<T, ushort>(ref leaf));
 293            return 0;
 294        }
 295
 296        byte WriteInt32(ref T leaf)
 297        {
 298            _writer.WriteInt32(Unsafe.As<T, int>(ref leaf));
 299            return 0;
 300        }
 301
 302        byte WriteUInt32(ref T leaf)
 303        {
 304            _writer.WriteUInt32(Unsafe.As<T, uint>(ref leaf));
 305            return 0;
 306        }
 307
 308        byte WriteInt64(ref T leaf)
 309        {
 310            _writer.WriteInt64(Unsafe.As<T, long>(ref leaf));
 311            return 0;
 312        }
 313
 314        byte WriteUInt64(ref T leaf)
 315        {
 316            _writer.WriteUInt64(Unsafe.As<T, ulong>(ref leaf));
 317            return 0;
 318        }
 319
 320        byte WriteChar(ref T leaf)
 321        {
 322            _writer.WriteChar(Unsafe.As<T, char>(ref leaf));
 323            return 0;
 324        }
 325
 326        byte WriteEnum(ref T leaf)
 327        {
 328            WriteEnumValue(ref leaf);
 329            return 0;
 330        }
 331    }
 332
 333    private void WriteEnumValue<T>(ref T value)
 334    {
 73888335        if (Unsafe.SizeOf<T>() == 1)
 336        {
 40337            _writer.WriteByte(Unsafe.As<T, byte>(ref value));
 40338            return;
 339        }
 340
 73848341        if (Unsafe.SizeOf<T>() == 2)
 342        {
 40343            _writer.WriteUInt16(Unsafe.As<T, ushort>(ref value));
 40344            return;
 345        }
 346
 73808347        if (Unsafe.SizeOf<T>() == 4)
 348        {
 73768349            _writer.WriteUInt32(Unsafe.As<T, uint>(ref value));
 73768350            return;
 351        }
 352
 40353        _writer.WriteUInt64(Unsafe.As<T, ulong>(ref value));
 40354    }
 355
 356    private static string GetStableTypeName(Type type)
 357    {
 553444358        lock (StableTypeNameSync)
 359        {
 553444360            if (StableTypeNames.TryGetValue(type, out string? name))
 553400361                return name;
 362
 44363            name = BuildStableTypeName(type);
 44364            StableTypeNames[type] = name;
 44365            return name;
 366        }
 553444367    }
 368
 369    private static string BuildStableTypeName(Type type)
 370    {
 44371        if (type.IsArray)
 2372            return BuildStableArrayTypeName(type);
 373
 42374        if (type.IsGenericParameter)
 0375            return type.Name;
 376
 42377        if (!type.IsGenericType)
 38378            return type.FullName ?? type.Name;
 379
 4380        return BuildStableGenericTypeName(type);
 381    }
 382
 383    private static string BuildStableArrayTypeName(Type type)
 384    {
 2385        Type elementType = type.GetElementType()!;
 2386        int rank = type.GetArrayRank();
 2387        string suffix = rank == 1
 2388            ? "[]"
 2389            : "[" + new string(',', rank - 1) + "]";
 390
 2391        return GetStableTypeName(elementType) + suffix;
 392    }
 393
 394    private static string BuildStableGenericTypeName(Type type)
 395    {
 4396        Type genericTypeDefinition = type.GetGenericTypeDefinition();
 4397        string baseName = genericTypeDefinition.FullName ?? genericTypeDefinition.Name;
 4398        Type[] arguments = type.GetGenericArguments();
 399
 4400        var builder = new StringBuilder(baseName.Length + (arguments.Length * 16) + 2);
 4401        builder.Append(baseName);
 4402        builder.Append('<');
 403
 22404        for (int i = 0; i < arguments.Length; i++)
 405        {
 7406            if (i > 0)
 3407                builder.Append(',');
 408
 7409            builder.Append(GetStableTypeName(arguments[i]));
 410        }
 411
 4412        builder.Append('>');
 4413        return builder.ToString();
 414    }
 415
 416    private static string FormatSlot(string? slot)
 417    {
 2418        return string.IsNullOrEmpty(slot)
 2419            ? string.Empty
 2420            : $" in slot '{slot}'";
 421    }
 422}