| | | 1 | | using System; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | using System.Runtime.CompilerServices; |
| | | 4 | | using System.Text; |
| | | 5 | | |
| | | 6 | | namespace Chronicler; |
| | | 7 | | |
| | | 8 | | internal 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 | | |
| | 1 | 38 | | private static readonly Dictionary<Type, LeafKind> LeafKindsByType = new() |
| | 1 | 39 | | { |
| | 1 | 40 | | [typeof(bool)] = LeafKind.Bool, |
| | 1 | 41 | | [typeof(byte)] = LeafKind.Byte, |
| | 1 | 42 | | [typeof(sbyte)] = LeafKind.SByte, |
| | 1 | 43 | | [typeof(short)] = LeafKind.Int16, |
| | 1 | 44 | | [typeof(ushort)] = LeafKind.UInt16, |
| | 1 | 45 | | [typeof(int)] = LeafKind.Int32, |
| | 1 | 46 | | [typeof(uint)] = LeafKind.UInt32, |
| | 1 | 47 | | [typeof(long)] = LeafKind.Int64, |
| | 1 | 48 | | [typeof(ulong)] = LeafKind.UInt64, |
| | 1 | 49 | | [typeof(char)] = LeafKind.Char, |
| | 1 | 50 | | [typeof(string)] = LeafKind.String |
| | 1 | 51 | | }; |
| | | 52 | | |
| | 1 | 53 | | private static readonly Dictionary<Type, string> StableTypeNames = new(); |
| | 1 | 54 | | private static readonly object StableTypeNameSync = new(); |
| | | 55 | | |
| | | 56 | | private ChronicleHashWriter _writer; |
| | | 57 | | private ChronicleContext? _context; |
| | | 58 | | private bool _isInUse; |
| | | 59 | | |
| | 4 | 60 | | private ChronicleHashChronicler() |
| | | 61 | | { |
| | 4 | 62 | | } |
| | | 63 | | |
| | | 64 | | public ChronicleContext Context |
| | | 65 | | { |
| | | 66 | | get |
| | | 67 | | { |
| | 5 | 68 | | if (_context == null) |
| | 0 | 69 | | throw new InvalidOperationException("Record hash chronicler context is not active."); |
| | | 70 | | |
| | 5 | 71 | | return _context; |
| | | 72 | | } |
| | | 73 | | } |
| | | 74 | | |
| | 36874 | 75 | | public SerializationMode Mode => SerializationMode.Saving; |
| | | 76 | | |
| | | 77 | | public static void Contribute(IRecordable target, ChronicleContext context, ref ChronicleHashWriter writer) |
| | | 78 | | { |
| | 73783 | 79 | | ChronicleHashChronicler chronicler = Rent(); |
| | | 80 | | |
| | 73783 | 81 | | chronicler._writer = writer; |
| | 73783 | 82 | | chronicler._context = context; |
| | | 83 | | |
| | | 84 | | try |
| | | 85 | | { |
| | 73783 | 86 | | chronicler.WriteRecordStart(target.GetType()); |
| | 73783 | 87 | | target.RecordData(chronicler); |
| | 73779 | 88 | | chronicler.WriteRecordEnd(); |
| | 73779 | 89 | | writer = chronicler._writer; |
| | 73779 | 90 | | } |
| | | 91 | | finally |
| | | 92 | | { |
| | 73783 | 93 | | chronicler._writer = default; |
| | 73783 | 94 | | chronicler._context = null; |
| | 73783 | 95 | | chronicler._isInUse = false; |
| | 73783 | 96 | | } |
| | 73779 | 97 | | } |
| | | 98 | | |
| | | 99 | | public void LookValue<T>(ref T value, string name, T? defaultValue = default) |
| | | 100 | | { |
| | 221470 | 101 | | WriteFieldHeader(name, FieldKind.Value, typeof(T)); |
| | 221469 | 102 | | WriteLeafKind(typeof(T), name); |
| | 221468 | 103 | | WriteLeafValue(ref value, name); |
| | | 104 | | |
| | 221468 | 105 | | T declaredDefault = defaultValue!; |
| | 221468 | 106 | | WriteLeafValue(ref declaredDefault, name); |
| | 221468 | 107 | | } |
| | | 108 | | |
| | | 109 | | public void LookDeep<T>(ref T value, string name) |
| | | 110 | | where T : class, IRecordable |
| | | 111 | | { |
| | 36874 | 112 | | WriteFieldHeader(name, FieldKind.DeepClass, typeof(T)); |
| | | 113 | | |
| | 36874 | 114 | | if (value == null) |
| | | 115 | | { |
| | 1 | 116 | | _writer.WriteBool(false); |
| | 1 | 117 | | return; |
| | | 118 | | } |
| | | 119 | | |
| | 36873 | 120 | | _writer.WriteBool(true); |
| | 36873 | 121 | | WriteRecordStart(typeof(T)); |
| | 36873 | 122 | | value.RecordData(this); |
| | 36873 | 123 | | WriteRecordEnd(); |
| | 36873 | 124 | | } |
| | | 125 | | |
| | | 126 | | public void LookDeepStruct<T>(ref T value, string name) |
| | | 127 | | where T : struct, IRecordable |
| | | 128 | | { |
| | 36872 | 129 | | WriteFieldHeader(name, FieldKind.DeepStruct, typeof(T)); |
| | 36872 | 130 | | WriteRecordStart(typeof(T)); |
| | 36872 | 131 | | value.RecordData(this); |
| | 36872 | 132 | | WriteRecordEnd(); |
| | 36872 | 133 | | } |
| | | 134 | | |
| | | 135 | | public void LookNullableDeep<T>(ref T? value, string name) |
| | | 136 | | where T : struct, IRecordable |
| | | 137 | | { |
| | 36872 | 138 | | WriteFieldHeader(name, FieldKind.NullableDeepStruct, typeof(T)); |
| | | 139 | | |
| | 36872 | 140 | | if (!value.HasValue) |
| | | 141 | | { |
| | 3 | 142 | | _writer.WriteBool(false); |
| | 3 | 143 | | return; |
| | | 144 | | } |
| | | 145 | | |
| | 36869 | 146 | | _writer.WriteBool(true); |
| | 36869 | 147 | | T nestedValue = value.Value; |
| | 36869 | 148 | | WriteRecordStart(typeof(T)); |
| | 36869 | 149 | | nestedValue.RecordData(this); |
| | 36869 | 150 | | WriteRecordEnd(); |
| | 36869 | 151 | | } |
| | | 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 | | { |
| | 6 | 160 | | string? normalizedSlot = string.IsNullOrEmpty(slot) ? null : slot; |
| | 6 | 161 | | WriteFieldHeader(name, FieldKind.Link, typeof(T)); |
| | 6 | 162 | | _writer.WriteString(normalizedSlot); |
| | 6 | 163 | | _writer.WriteEnum(resolveMode); |
| | | 164 | | |
| | 6 | 165 | | if (value is null) |
| | | 166 | | { |
| | 1 | 167 | | _writer.WriteString(null); |
| | 1 | 168 | | return; |
| | | 169 | | } |
| | | 170 | | |
| | 5 | 171 | | if (!Context.Links.TryGetReferenceId(value, out string? id, normalizedSlot)) |
| | | 172 | | { |
| | 2 | 173 | | throw new InvalidOperationException( |
| | 2 | 174 | | $"Unable to hash link '{name}' of type {typeof(T).Name} because no stable id could be produced{FormatSlo |
| | | 175 | | } |
| | | 176 | | |
| | 3 | 177 | | _writer.WriteString(id); |
| | 3 | 178 | | } |
| | | 179 | | |
| | | 180 | | private static ChronicleHashChronicler Rent() |
| | | 181 | | { |
| | 73783 | 182 | | ChronicleHashChronicler? cached = _cached; |
| | 73783 | 183 | | if (cached == null) |
| | | 184 | | { |
| | 3 | 185 | | cached = new ChronicleHashChronicler(); |
| | 3 | 186 | | _cached = cached; |
| | | 187 | | } |
| | | 188 | | |
| | 73783 | 189 | | if (cached._isInUse) |
| | 1 | 190 | | return new ChronicleHashChronicler { _isInUse = true }; |
| | | 191 | | |
| | 73782 | 192 | | cached._isInUse = true; |
| | 73782 | 193 | | return cached; |
| | | 194 | | } |
| | | 195 | | |
| | | 196 | | private void WriteRecordStart(Type declaredType) |
| | | 197 | | { |
| | 184397 | 198 | | _writer.WriteSection("chronicler.record", 1); |
| | 184397 | 199 | | _writer.WriteString(GetStableTypeName(declaredType)); |
| | 184397 | 200 | | } |
| | | 201 | | |
| | | 202 | | private void WriteRecordEnd() |
| | | 203 | | { |
| | 184393 | 204 | | _writer.WriteSection("chronicler.record.end", 1); |
| | 184393 | 205 | | } |
| | | 206 | | |
| | | 207 | | private void WriteFieldHeader(string name, FieldKind kind, Type declaredType) |
| | | 208 | | { |
| | 332094 | 209 | | if (name == null) |
| | 1 | 210 | | throw new ArgumentNullException(nameof(name)); |
| | | 211 | | |
| | 332093 | 212 | | _writer.WriteString(name); |
| | 332093 | 213 | | _writer.WriteByte((byte)kind); |
| | 332093 | 214 | | _writer.WriteString(GetStableTypeName(declaredType)); |
| | 332093 | 215 | | } |
| | | 216 | | |
| | | 217 | | private void WriteLeafKind(Type declaredType, string name) |
| | | 218 | | { |
| | 221469 | 219 | | LeafKind kind = GetLeafKind(declaredType, name); |
| | 221468 | 220 | | _writer.WriteByte((byte)kind); |
| | | 221 | | |
| | 221468 | 222 | | if (kind == LeafKind.Enum) |
| | 36944 | 223 | | _writer.WriteString(GetStableTypeName(Enum.GetUnderlyingType(declaredType))); |
| | 221468 | 224 | | } |
| | | 225 | | |
| | | 226 | | private static LeafKind GetLeafKind(Type declaredType, string name) |
| | | 227 | | { |
| | 664405 | 228 | | if (declaredType.IsEnum) |
| | 110832 | 229 | | return LeafKind.Enum; |
| | | 230 | | |
| | 553573 | 231 | | if (LeafKindsByType.TryGetValue(declaredType, out LeafKind kind)) |
| | 553572 | 232 | | return kind; |
| | | 233 | | |
| | 1 | 234 | | 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 | | { |
| | 442936 | 239 | | Type declaredType = typeof(T); |
| | 442936 | 240 | | LeafKind kind = GetLeafKind(declaredType, name); |
| | 442936 | 241 | | if (kind == LeafKind.String) |
| | | 242 | | { |
| | 73776 | 243 | | _writer.WriteString(value as string); |
| | 73776 | 244 | | return; |
| | | 245 | | } |
| | | 246 | | |
| | 369160 | 247 | | _writer.WriteBool(true); |
| | | 248 | | |
| | 369160 | 249 | | _ = kind switch |
| | 369160 | 250 | | { |
| | 147508 | 251 | | LeafKind.Bool => WriteBool(ref value), |
| | 26 | 252 | | LeafKind.Byte => WriteByte(ref value), |
| | 26 | 253 | | LeafKind.SByte => WriteSByte(ref value), |
| | 26 | 254 | | LeafKind.Int16 => WriteInt16(ref value), |
| | 26 | 255 | | LeafKind.UInt16 => WriteUInt16(ref value), |
| | 147556 | 256 | | LeafKind.Int32 => WriteInt32(ref value), |
| | 26 | 257 | | LeafKind.UInt32 => WriteUInt32(ref value), |
| | 26 | 258 | | LeafKind.Int64 => WriteInt64(ref value), |
| | 26 | 259 | | LeafKind.UInt64 => WriteUInt64(ref value), |
| | 26 | 260 | | LeafKind.Char => WriteChar(ref value), |
| | 73888 | 261 | | LeafKind.Enum => WriteEnum(ref value), |
| | 0 | 262 | | _ => throw new NotSupportedException( |
| | 0 | 263 | | $"Unsupported record-hash leaf value '{name}' of type {GetStableTypeName(declaredType)}.") |
| | 369160 | 264 | | }; |
| | | 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 | | { |
| | 73888 | 335 | | if (Unsafe.SizeOf<T>() == 1) |
| | | 336 | | { |
| | 40 | 337 | | _writer.WriteByte(Unsafe.As<T, byte>(ref value)); |
| | 40 | 338 | | return; |
| | | 339 | | } |
| | | 340 | | |
| | 73848 | 341 | | if (Unsafe.SizeOf<T>() == 2) |
| | | 342 | | { |
| | 40 | 343 | | _writer.WriteUInt16(Unsafe.As<T, ushort>(ref value)); |
| | 40 | 344 | | return; |
| | | 345 | | } |
| | | 346 | | |
| | 73808 | 347 | | if (Unsafe.SizeOf<T>() == 4) |
| | | 348 | | { |
| | 73768 | 349 | | _writer.WriteUInt32(Unsafe.As<T, uint>(ref value)); |
| | 73768 | 350 | | return; |
| | | 351 | | } |
| | | 352 | | |
| | 40 | 353 | | _writer.WriteUInt64(Unsafe.As<T, ulong>(ref value)); |
| | 40 | 354 | | } |
| | | 355 | | |
| | | 356 | | private static string GetStableTypeName(Type type) |
| | | 357 | | { |
| | 553444 | 358 | | lock (StableTypeNameSync) |
| | | 359 | | { |
| | 553444 | 360 | | if (StableTypeNames.TryGetValue(type, out string? name)) |
| | 553400 | 361 | | return name; |
| | | 362 | | |
| | 44 | 363 | | name = BuildStableTypeName(type); |
| | 44 | 364 | | StableTypeNames[type] = name; |
| | 44 | 365 | | return name; |
| | | 366 | | } |
| | 553444 | 367 | | } |
| | | 368 | | |
| | | 369 | | private static string BuildStableTypeName(Type type) |
| | | 370 | | { |
| | 44 | 371 | | if (type.IsArray) |
| | 2 | 372 | | return BuildStableArrayTypeName(type); |
| | | 373 | | |
| | 42 | 374 | | if (type.IsGenericParameter) |
| | 0 | 375 | | return type.Name; |
| | | 376 | | |
| | 42 | 377 | | if (!type.IsGenericType) |
| | 38 | 378 | | return type.FullName ?? type.Name; |
| | | 379 | | |
| | 4 | 380 | | return BuildStableGenericTypeName(type); |
| | | 381 | | } |
| | | 382 | | |
| | | 383 | | private static string BuildStableArrayTypeName(Type type) |
| | | 384 | | { |
| | 2 | 385 | | Type elementType = type.GetElementType()!; |
| | 2 | 386 | | int rank = type.GetArrayRank(); |
| | 2 | 387 | | string suffix = rank == 1 |
| | 2 | 388 | | ? "[]" |
| | 2 | 389 | | : "[" + new string(',', rank - 1) + "]"; |
| | | 390 | | |
| | 2 | 391 | | return GetStableTypeName(elementType) + suffix; |
| | | 392 | | } |
| | | 393 | | |
| | | 394 | | private static string BuildStableGenericTypeName(Type type) |
| | | 395 | | { |
| | 4 | 396 | | Type genericTypeDefinition = type.GetGenericTypeDefinition(); |
| | 4 | 397 | | string baseName = genericTypeDefinition.FullName ?? genericTypeDefinition.Name; |
| | 4 | 398 | | Type[] arguments = type.GetGenericArguments(); |
| | | 399 | | |
| | 4 | 400 | | var builder = new StringBuilder(baseName.Length + (arguments.Length * 16) + 2); |
| | 4 | 401 | | builder.Append(baseName); |
| | 4 | 402 | | builder.Append('<'); |
| | | 403 | | |
| | 22 | 404 | | for (int i = 0; i < arguments.Length; i++) |
| | | 405 | | { |
| | 7 | 406 | | if (i > 0) |
| | 3 | 407 | | builder.Append(','); |
| | | 408 | | |
| | 7 | 409 | | builder.Append(GetStableTypeName(arguments[i])); |
| | | 410 | | } |
| | | 411 | | |
| | 4 | 412 | | builder.Append('>'); |
| | 4 | 413 | | return builder.ToString(); |
| | | 414 | | } |
| | | 415 | | |
| | | 416 | | private static string FormatSlot(string? slot) |
| | | 417 | | { |
| | 2 | 418 | | return string.IsNullOrEmpty(slot) |
| | 2 | 419 | | ? string.Empty |
| | 2 | 420 | | : $" in slot '{slot}'"; |
| | | 421 | | } |
| | | 422 | | } |