From f3a091ce102551fe8e25551d577fdc76c822f0d5 Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Wed, 27 Dec 2023 19:23:33 -0500 Subject: [PATCH] init --- .../Binary/Builders/ObjectBuilder.cs | 16 +- .../Binary/Codecs/ArgumentCodecContext.cs | 12 + .../Binary/Codecs/ArrayCodec.cs | 14 +- .../Binary/Codecs/CodecContext.cs | 2 +- .../Binary/Codecs/CodecFormatter.cs | 39 +++ .../Binary/Codecs/CompilableCodec.cs | 10 +- .../Binary/Codecs/Impl/BaseArgumentCodec.cs | 4 +- .../Binary/Codecs/Impl/IArgumentCodec.cs | 4 +- .../Binary/Codecs/Impl/ICompiledCodec.cs | 7 + .../Binary/Codecs/MultiRangeCodec.cs | 24 +- .../Binary/Codecs/NullCodec.cs | 2 +- .../Binary/Codecs/ObjectCodec.cs | 21 +- .../Binary/Codecs/RangeCodec.cs | 23 +- .../Binary/Codecs/SetCodec.cs | 14 +- .../Binary/Codecs/SparceObjectCodec.cs | 12 - .../Binary/Codecs/Visitors/ArgumentVisitor.cs | 50 ++++ .../Binary/Codecs/Visitors/CodecVisitor.cs | 6 +- .../Binary/Codecs/Visitors/TypeVisitor.cs | 254 ++++++++---------- .../Protocol/V1.0/V1ProtocolProvider.cs | 43 ++- .../Clients/EdgeDBBinaryClient.cs | 23 +- .../Extensions/CodecExtensions.cs | 12 - src/EdgeDB.Net.Driver/Log.cs | 12 +- src/EdgeDB.Net.Driver/Utils/Ref.cs | 10 + .../CodecVisitorBenchmarks.cs | 16 ++ 24 files changed, 412 insertions(+), 218 deletions(-) create mode 100644 src/EdgeDB.Net.Driver/Binary/Codecs/ArgumentCodecContext.cs create mode 100644 src/EdgeDB.Net.Driver/Binary/Codecs/Impl/ICompiledCodec.cs create mode 100644 src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/ArgumentVisitor.cs create mode 100644 src/EdgeDB.Net.Driver/Utils/Ref.cs create mode 100644 tests/EdgeDB.Tests.Benchmarks/CodecVisitorBenchmarks.cs diff --git a/src/EdgeDB.Net.Driver/Binary/Builders/ObjectBuilder.cs b/src/EdgeDB.Net.Driver/Binary/Builders/ObjectBuilder.cs index 1e212f71..14be060a 100644 --- a/src/EdgeDB.Net.Driver/Binary/Builders/ObjectBuilder.cs +++ b/src/EdgeDB.Net.Driver/Binary/Builders/ObjectBuilder.cs @@ -1,5 +1,6 @@ using EdgeDB.Binary.Codecs; using EdgeDB.DataTypes; +using EdgeDB.Utils; using Microsoft.Extensions.Logging; using System.Collections; using System.Collections.Concurrent; @@ -12,7 +13,7 @@ internal sealed class ObjectBuilder private static readonly ConcurrentDictionary _codecVisitorStateTable = new(); private static readonly object _visitorLock = new(); - public static PreheatedCodec PreheatCodec(EdgeDBBinaryClient client, ICodec codec) + public static async Task PreheatCodec(EdgeDBBinaryClient client, ICodec codec) { // if the codec has been visited before and we have the most up-to-date version, return it. if ( @@ -28,17 +29,18 @@ public static PreheatedCodec PreheatCodec(EdgeDBBinaryClient client, ICodec c var visitor = new TypeVisitor(client); visitor.SetTargetType(typeof(T)); - visitor.Visit(ref codec); + var reference = new Ref(codec); + await visitor.VisitAsync(reference); if (typeof(T) != typeof(object)) - _codecVisitorStateTable[typeof(T)] = (version, codec); + _codecVisitorStateTable[typeof(T)] = (version, reference.Value); if (client.Logger.IsEnabled(LogLevel.Debug)) { - client.Logger.ObjectDeserializationPrep(CodecFormatter.Format(codec).ToString()); + client.Logger.ObjectDeserializationPrep(CodecFormatter.Format(reference.Value).ToString()); } - return new PreheatedCodec(codec); + return new PreheatedCodec(reference.Value); } public static T? BuildResult(EdgeDBBinaryClient client, in PreheatedCodec preheated, @@ -54,8 +56,8 @@ public static PreheatedCodec PreheatCodec(EdgeDBBinaryClient client, ICodec c return (T?)ConvertTo(typeof(T), value); } - public static T? BuildResult(EdgeDBBinaryClient client, ICodec codec, in ReadOnlyMemory data) - => BuildResult(client, PreheatCodec(client, codec), data); + public static async Task BuildResultAsync(EdgeDBBinaryClient client, ICodec codec, ReadOnlyMemory data) + => BuildResult(client, await PreheatCodec(client, codec), data); public static object? ConvertTo(Type type, object? value) { diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/ArgumentCodecContext.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/ArgumentCodecContext.cs new file mode 100644 index 00000000..9a897c20 --- /dev/null +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/ArgumentCodecContext.cs @@ -0,0 +1,12 @@ +namespace EdgeDB.Binary.Codecs; + +internal sealed class ArgumentCodecContext : CodecContext +{ + public readonly ICodec[] Codecs; + + public ArgumentCodecContext(ICodec[] codecs, EdgeDBBinaryClient client) + : base(client) + { + Codecs = codecs; + } +} diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/ArrayCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/ArrayCodec.cs index 51abb2a5..7f7e3077 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/ArrayCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/ArrayCodec.cs @@ -3,15 +3,25 @@ namespace EdgeDB.Binary.Codecs; internal sealed class ArrayCodec - : BaseCodec, IWrappingCodec, ICacheableCodec + : BaseCodec, IWrappingCodec, ICacheableCodec, ICompiledCodec { public static readonly byte[] EMPTY_ARRAY = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + public Type CompiledFrom { get; } + public CompilableWrappingCodec Template { get; } + internal ICodec InnerCodec; - public ArrayCodec(in Guid id, ICodec innerCodec, CodecMetadata? metadata = null) + public ArrayCodec( + in Guid id, + Type compiledFrom, + CompilableWrappingCodec template, + ICodec innerCodec, + CodecMetadata? metadata = null) : base(in id, metadata) { + CompiledFrom = compiledFrom; + Template = template; InnerCodec = innerCodec; } diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/CodecContext.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/CodecContext.cs index 618e2f28..22a89b9c 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/CodecContext.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/CodecContext.cs @@ -2,7 +2,7 @@ namespace EdgeDB.Binary.Codecs; -internal sealed class CodecContext +internal class CodecContext { public CodecContext(EdgeDBBinaryClient client) { diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/CodecFormatter.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/CodecFormatter.cs index be509915..0ff2944c 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/CodecFormatter.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/CodecFormatter.cs @@ -5,6 +5,45 @@ namespace EdgeDB.Binary.Codecs; internal static class CodecFormatter { + public static StringBuilder FormatCodecAsTree(ICodec codec) + { + var sb = new StringBuilder(); + + AppendCodecToTree(sb, codec, 0); + + return sb; + } + + + private const int CODEC_TREE_SPACING = 2; + private static void AppendCodecToTree(StringBuilder tree, ICodec codec, int depth, string? prefix = null) + { + tree.AppendLine("".PadLeft(depth) + $"{prefix} {codec}"); + + if (codec is IMultiWrappingCodec multiwrap) + { + for (int i = 0; i != multiwrap.InnerCodecs.Length; i++) + { + var innerCodec = multiwrap.InnerCodecs[i]; + AppendCodecToTree( + tree, + innerCodec, + depth + CODEC_TREE_SPACING, + i == multiwrap.InnerCodecs.Length - 1 ? "\u2514" : "\u251c" + ); + } + } + else if (codec is IWrappingCodec wrapping) + { + AppendCodecToTree( + tree, + wrapping.InnerCodec, + depth + CODEC_TREE_SPACING, + "\u2514" + ); + } + } + public static StringBuilder Format(ICodec codec, int spacing = 2) { StringBuilder sb = new(); diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/CompilableCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/CompilableCodec.cs index ab73ad35..86b7497c 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/CompilableCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/CompilableCodec.cs @@ -31,7 +31,7 @@ void ICodec.Serialize(ref PacketWriter writer, object? value, CodecContext conte object? ICodec.Deserialize(ref PacketReader reader, CodecContext context) => throw new NotSupportedException(); // to avoid state changes to this compilable, pass in the inner codec post-walk. - public ICodec Compile(IProtocolProvider provider, Type type, ICodec? innerCodec = null) + public ICompiledCodec Compile(IProtocolProvider provider, Type type, ICodec? innerCodec = null) { innerCodec ??= InnerCodec; @@ -41,7 +41,10 @@ public ICodec Compile(IProtocolProvider provider, Type type, ICodec? innerCodec return CodecBuilder.GetProviderCache(provider).CompiledCodecCache.GetOrAdd(cacheKey, k => { - var codec = (ICodec)Activator.CreateInstance(genType, Id, innerCodec, Metadata)!; + var codec = (ICompiledCodec)Activator.CreateInstance( + genType, + Id, type, this, innerCodec, Metadata + )!; if (codec is IComplexCodec complex) { @@ -49,7 +52,8 @@ public ICodec Compile(IProtocolProvider provider, Type type, ICodec? innerCodec } return codec; - }); + }) as ICompiledCodec + ?? throw new InvalidOperationException("Codec that was returned from the cache was not a compiled codec, this shouldn't happen"); } public Type GetInnerType() diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/BaseArgumentCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/BaseArgumentCodec.cs index 014fc061..35c0329d 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/BaseArgumentCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/BaseArgumentCodec.cs @@ -9,8 +9,8 @@ protected BaseArgumentCodec(in Guid id, CodecMetadata? metadata) { } - public abstract void SerializeArguments(ref PacketWriter writer, T? value, CodecContext context); + public abstract void SerializeArguments(ref PacketWriter writer, T? value, ArgumentCodecContext context); - void IArgumentCodec.SerializeArguments(ref PacketWriter writer, object? value, CodecContext context) + void IArgumentCodec.SerializeArguments(ref PacketWriter writer, object? value, ArgumentCodecContext context) => SerializeArguments(ref writer, (T?)value, context); } diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/IArgumentCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/IArgumentCodec.cs index b9b80da4..df406d0f 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/IArgumentCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/IArgumentCodec.cs @@ -2,10 +2,10 @@ namespace EdgeDB.Binary.Codecs; internal interface IArgumentCodec : IArgumentCodec, ICodec { - void SerializeArguments(ref PacketWriter writer, T? value, CodecContext context); + void SerializeArguments(ref PacketWriter writer, T? value, ArgumentCodecContext context); } internal interface IArgumentCodec { - void SerializeArguments(ref PacketWriter writer, object? value, CodecContext context); + void SerializeArguments(ref PacketWriter writer, object? value, ArgumentCodecContext context); } diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/ICompiledCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/ICompiledCodec.cs new file mode 100644 index 00000000..cae81217 --- /dev/null +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/Impl/ICompiledCodec.cs @@ -0,0 +1,7 @@ +namespace EdgeDB.Binary.Codecs; + +internal interface ICompiledCodec : ICodec, IWrappingCodec +{ + Type CompiledFrom { get; } + CompilableWrappingCodec Template { get; } +} diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs index e618d288..c1717d76 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/MultiRangeCodec.cs @@ -4,14 +4,32 @@ namespace EdgeDB.Binary.Codecs; -internal sealed class MultiRangeCodec : BaseCodec>, IWrappingCodec, ICacheableCodec +internal sealed class MultiRangeCodec + : BaseCodec>, IWrappingCodec, ICacheableCodec, ICompiledCodec where T : struct { + public Type CompiledFrom { get; } + public CompilableWrappingCodec Template { get; } + private RangeCodec _rangeCodec; - public MultiRangeCodec(in Guid id, ICodec rangeInnerCodec, CodecMetadata? metadata) : base(in id, metadata) + public MultiRangeCodec( + in Guid id, + Type compiledFrom, + CompilableWrappingCodec template, + ICodec rangeInnerCodec, + CodecMetadata? metadata + ) : base(in id, metadata) { - _rangeCodec = new RangeCodec(in id, rangeInnerCodec, metadata); + CompiledFrom = compiledFrom; + Template = template; + _rangeCodec = new RangeCodec( + in id, + compiledFrom, + template, + rangeInnerCodec, + metadata + ); } public override void Serialize(ref PacketWriter writer, MultiRange value, CodecContext context) diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/NullCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/NullCodec.cs index af101548..2075179b 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/NullCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/NullCodec.cs @@ -9,7 +9,7 @@ public NullCodec() { } public NullCodec(CodecMetadata? metadata = null) { } // used in generic codec construction - public void SerializeArguments(ref PacketWriter writer, object? value, CodecContext context) { } + public void SerializeArguments(ref PacketWriter writer, object? value, ArgumentCodecContext context) { } public Guid Id => Guid.Empty; diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/ObjectCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/ObjectCodec.cs index f919e04d..9a58bf33 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/ObjectCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/ObjectCodec.cs @@ -53,6 +53,9 @@ public EdgeDBTypeDeserializeInfo Deserializer reader.Position = enumerator.Reader.Position; } } + + public override string ToString() + => $"object ({TargetType.Name})"; } internal class ObjectCodec @@ -109,10 +112,13 @@ public TypeInitializedObjectCodec GetOrCreateTypeCodec(Type type) } } - public override void SerializeArguments(ref PacketWriter writer, object? value, CodecContext context) - => Serialize(ref writer, value, context); + public override void SerializeArguments(ref PacketWriter writer, object? value, ArgumentCodecContext context) + => Serialize(ref writer, value, context.Codecs, context); public override void Serialize(ref PacketWriter writer, object? value, CodecContext context) + => Serialize(ref writer, value, InnerCodecs, context); + + private void Serialize(ref PacketWriter writer, object? value, ICodec[] codecs, CodecContext context) { object?[]? values = null; @@ -128,9 +134,6 @@ public override void Serialize(ref PacketWriter writer, object? value, CodecCont writer.Write(values.Length); - // TODO: maybe cache the visited codecs based on the 'value'. - var visitor = context.CreateTypeVisitor(); - for (var i = 0; i != values.Length; i++) { var element = values[i]; @@ -156,17 +159,11 @@ public override void Serialize(ref PacketWriter writer, object? value, CodecCont } else { - var innerCodec = InnerCodecs[i]; + var innerCodec = codecs[i]; // special case for enums if (element.GetType().IsEnum && innerCodec is TextCodec) element = element.ToString(); - else - { - visitor.SetTargetType(element.GetType()); - visitor.Visit(ref innerCodec); - visitor.Reset(); - } writer.WriteToWithInt32Length((ref PacketWriter innerWriter) => innerCodec.Serialize(ref innerWriter, element, context)); diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/RangeCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/RangeCodec.cs index b6811cfc..a0aae6e7 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/RangeCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/RangeCodec.cs @@ -5,26 +5,37 @@ namespace EdgeDB.Binary.Codecs; internal sealed class RangeCodec - : BaseComplexCodec>, IWrappingCodec, ICacheableCodec + : BaseComplexCodec>, IWrappingCodec, ICacheableCodec, ICompiledCodec where T : struct { [Flags] public enum RangeFlags : byte { Empty = 1 << 0, - IncudeLowerBound = 1 << 1, + IncludeLowerBound = 1 << 1, IncludeUpperBound = 1 << 2, InfiniteLowerBound = 1 << 3, InfiniteUpperBound = 1 << 4 } - public ICodec _innerCodec; + public Type CompiledFrom { get; } + public CompilableWrappingCodec Template { get; } - public RangeCodec(in Guid id, ICodec innerCodec, CodecMetadata? metadata = null) + private ICodec _innerCodec; + + public RangeCodec( + in Guid id, + Type compiledFrom, + CompilableWrappingCodec template, + ICodec innerCodec, + CodecMetadata? metadata = null) : base(in id, metadata) { _innerCodec = innerCodec; + CompiledFrom = compiledFrom; + Template = template; + AddConverter(From, To); } @@ -108,7 +119,7 @@ public override Range Deserialize(ref PacketReader reader, CodecContext conte upperBound = _innerCodec.Deserialize(ref reader, context); } - return new Range(lowerBound, upperBound, (flags & RangeFlags.IncudeLowerBound) != 0, + return new Range(lowerBound, upperBound, (flags & RangeFlags.IncludeLowerBound) != 0, (flags & RangeFlags.IncludeUpperBound) != 0); } @@ -116,7 +127,7 @@ public override void Serialize(ref PacketWriter writer, Range value, CodecCon { var flags = value.IsEmpty ? RangeFlags.Empty - : (value.IncludeLower ? RangeFlags.IncudeLowerBound : 0) | + : (value.IncludeLower ? RangeFlags.IncludeLowerBound : 0) | (value.IncludeUpper ? RangeFlags.IncludeUpperBound : 0) | (!value.Lower.HasValue ? RangeFlags.InfiniteLowerBound : 0) | (!value.Upper.HasValue ? RangeFlags.InfiniteUpperBound : 0); diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/SetCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/SetCodec.cs index 9ae2a590..715220c0 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/SetCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/SetCodec.cs @@ -3,15 +3,25 @@ namespace EdgeDB.Binary.Codecs; internal sealed class SetCodec - : BaseCodec>, IWrappingCodec, ICacheableCodec + : BaseCodec>, IWrappingCodec, ICacheableCodec, ICompiledCodec { + public Type CompiledFrom { get; } + public CompilableWrappingCodec Template { get; } + private readonly bool _isSetOfArray; internal ICodec InnerCodec; - public SetCodec(in Guid id, ICodec innerCodec, CodecMetadata? metadata = null) + public SetCodec( + in Guid id, + Type compiledFrom, + CompilableWrappingCodec template, + ICodec innerCodec, + CodecMetadata? metadata = null) : base(in id, metadata) { InnerCodec = innerCodec; + CompiledFrom = compiledFrom; + Template = template; var codecType = innerCodec.GetType(); _isSetOfArray = codecType.IsGenericType && codecType.GetGenericTypeDefinition() == typeof(ArrayCodec<>); } diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/SparceObjectCodec.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/SparceObjectCodec.cs index fd59d835..4bf4229a 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/SparceObjectCodec.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/SparceObjectCodec.cs @@ -74,8 +74,6 @@ public override void Serialize(ref PacketWriter writer, object? value, CodecCont writer.Write(dict.Count); - var visitor = context.CreateTypeVisitor(); - foreach (var element in dict) { var index = Array.IndexOf(FieldNames, element.Key); @@ -91,16 +89,6 @@ public override void Serialize(ref PacketWriter writer, object? value, CodecCont { var codec = InnerCodecs[index]; - // ignore nested sparce object codecs, they will be walked - // in their serialize method. - visitor.SetTargetType(codec is SparceObjectCodec - ? typeof(void) - : element.Value.GetType() - ); - - visitor.Visit(ref codec); - visitor.Reset(); - writer.WriteToWithInt32Length((ref PacketWriter innerWriter) => codec.Serialize(ref innerWriter, element.Value, context)); } diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/ArgumentVisitor.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/ArgumentVisitor.cs new file mode 100644 index 00000000..6e7cee2f --- /dev/null +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/ArgumentVisitor.cs @@ -0,0 +1,50 @@ +using EdgeDB.Utils; + +namespace EdgeDB.Binary.Codecs; + +internal sealed class ArgumentVisitor : CodecVisitor +{ + public ICodec[] VisitedChildCodecs { get; private set; } = Array.Empty(); + private readonly IDictionary _arguments; + private readonly EdgeDBBinaryClient _client; + + public ArgumentVisitor(EdgeDBBinaryClient client, IDictionary args) + { + _client = client; + _arguments = args; + } + + protected override async Task VisitCodecAsync(Ref codec) + { + switch (codec.Value) + { + case ObjectCodec objectCodec: + VisitedChildCodecs = await Task.WhenAll( + objectCodec.InnerCodecs.Select(async (x, i) => + { + var type = typeof(object); + + if (_arguments.TryGetValue(objectCodec.PropertyNames[i], out var v)) + type = v?.GetType() ?? typeof(object); + + var reference = new Ref(x); + await VisitAsync(reference, type); + + return reference.Value; + }) + ); + break; + case NullCodec: + break; + default: + throw new InvalidOperationException($"Got unexpected argument codec \"{codec.Value}\""); + } + } + + private async ValueTask VisitAsync(Ref codec, Type type) + { + var typeVisitor = new TypeVisitor(_client); + typeVisitor.SetTargetType(type); + await typeVisitor.VisitAsync(codec); + } +} diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/CodecVisitor.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/CodecVisitor.cs index 6ff72306..03632e9f 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/CodecVisitor.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/CodecVisitor.cs @@ -1,8 +1,10 @@ +using EdgeDB.Utils; + namespace EdgeDB.Binary.Codecs; internal abstract class CodecVisitor { - public void Visit(ref ICodec codec) => VisitCodec(ref codec); + public Task VisitAsync(Ref codec) => VisitCodecAsync(codec); - protected abstract void VisitCodec(ref ICodec codec); + protected abstract Task VisitCodecAsync(Ref codec); } diff --git a/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/TypeVisitor.cs b/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/TypeVisitor.cs index 1365de3e..de4d9d50 100644 --- a/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/TypeVisitor.cs +++ b/src/EdgeDB.Net.Driver/Binary/Codecs/Visitors/TypeVisitor.cs @@ -1,5 +1,8 @@ using EdgeDB.DataTypes; +using EdgeDB.Utils; using Microsoft.Extensions.Logging; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using DateTime = System.DateTime; @@ -9,126 +12,146 @@ internal sealed class TypeVisitor : CodecVisitor { private readonly EdgeDBBinaryClient _client; - private readonly Stack _frames; private readonly ILogger _logger; + private Type? _targetType; + private TypeVisitorContext? _context; + public TypeVisitor(EdgeDBBinaryClient client) { - _frames = new Stack(); _logger = client.Logger; _client = client; } - private TypeResultFrame Context - => _frames.Peek(); - - private string Depth - => "".PadRight(_frames.Count, '>') + (_frames.Count > 0 ? " " : ""); - public void SetTargetType(Type type) { - EdgeDBTypeDeserializeInfo? deserializer = null; - - if (type != typeof(void)) - { - _ = TypeBuilder.IsValidObjectType(type) && TypeBuilder.TryGetTypeDeserializerInfo(type, out deserializer); - } - - var old = _frames.Count == 0 ? typeof(void) : Context.Type; - - _logger.CodecVisitorStackTransition(Depth, old, type); - - _frames.Push(new TypeResultFrame {Type = type, Deserializer = deserializer}); + _targetType = type; + _context = new TypeVisitorContext(type); } - public void Reset() - => _frames.Clear(); + protected override +#if DEBUG + async +#endif + Task VisitCodecAsync(Ref codec) + { + if (_context is null) + throw new EdgeDBException("Context was not initialized for type walking"); + +#if DEBUG + var sw = Stopwatch.StartNew(); + await VisitCodecAsync(codec, _context); + sw.Stop(); + _logger.CodecVisitorTimingTrace( + this, + Math.Round(sw.Elapsed.TotalMilliseconds, 4), + CodecFormatter.FormatCodecAsTree(codec.Value).ToString() + ); +#else + if(_logger.IsEnabled(LogLevel.Trace)) + _logger.CodecTree(CodecFormatter.FormatCodecAsTree(codec.Value).ToString()); + return VisitCodecAsync(codec, _context); +#endif + } - protected override void VisitCodec(ref ICodec codec) + private async Task VisitCodecAsync(Ref codec, TypeVisitorContext context) { - // TODO: if dynamic or object was passed in, return the default type + // TODO: if dynamic or object was passed in, return the default type // from the complex OR based on config. - if (Context.Type == typeof(void)) + if (context.Type == typeof(void)) return; - _logger.CodecVisitorNewCodec(Depth, codec); + _logger.CodecVisitorNewCodec(context.FormattedDepth, codec.Value); - switch (codec) + switch (codec.Value) { + case ICompiledCodec compiled when (context.Type != compiled.ConverterType): + { + var reference = new Ref(compiled.InnerCodec); + + var type = context.IsDynamicResult + ? context.Type + : context.Type.GetWrappingType(); + + await VisitCodecAsync(reference, context with { Type = type }); + compiled.InnerCodec = reference.Value; + } + break; case ObjectCodec obj: { if (obj is TypeInitializedObjectCodec typeCodec) { - if (typeCodec.TargetType != Context.Type) - typeCodec = typeCodec.Parent.GetOrCreateTypeCodec(Context.Type); + if (typeCodec.TargetType != context.Type) + typeCodec = typeCodec.Parent.GetOrCreateTypeCodec(context.Type); } else { - typeCodec = obj.GetOrCreateTypeCodec(Context.Type); + typeCodec = obj.GetOrCreateTypeCodec(context.Type); } if (typeCodec.TargetType is null || typeCodec.Deserializer is null) throw new NullReferenceException("Could not find deserializer info for object codec."); - using var objHandle = EnterNewContext(typeCodec.TargetType, typeCodec.TargetType.Name, - typeCodec.Deserializer); - for (var i = 0; i != obj.InnerCodecs.Length; i++) + var subContext = context with { - ref var innerCodec = ref obj.InnerCodecs[i]; - var name = obj.PropertyNames[i]; + Type = typeCodec.TargetType, + Deserializer = typeCodec.Deserializer, + Name = typeCodec.TargetType.Name + }; + await Task.WhenAll(obj.InnerCodecs.Select(async (x, i) => + { // use the defined type, if not found, use the codecs type // if the inner is compilable, use its inner type and set the real - // flag, since compileable visits only care about the inner type rather + // flag, since compilable visits only care about the inner type rather // than a concrete root. - var hasPropInfo = Context.Deserializer!.PropertyMapInfo.Map.TryGetValue(name, out var propInfo); - - var propType = hasPropInfo - ? propInfo!.Type - : innerCodec is CompilableWrappingCodec compilable - ? compilable.GetInnerType() - : innerCodec.ConverterType; - - using var propHandle = EnterNewContext(propType, name, - innerRealType: !hasPropInfo && innerCodec is CompilableWrappingCodec); - - Visit(ref innerCodec); - - _logger.CodecVisitorMutatedCodec(Depth, obj.InnerCodecs[i], innerCodec); - } - - codec = typeCodec; + var hasPropInfo = + typeCodec.Deserializer.PropertyMapInfo.Map.TryGetValue(obj.PropertyNames[i], out var propInfo); + + var reference = new Ref(x); + + await VisitCodecAsync(reference, subContext with + { + Depth = subContext.Depth + 1, + Type = hasPropInfo + ? propInfo!.Type + : x is CompilableWrappingCodec compilable + ? compilable.GetInnerType() + : x.ConverterType, + InnerRealType = !hasPropInfo && x is CompilableWrappingCodec, + Name = obj.PropertyNames[i] + }); + + obj.InnerCodecs[i] = reference.Value; + })); + + codec.Value = typeCodec; } - break; + break; case TupleCodec tuple: { - var tupleTypes = Context.Type.IsAssignableTo(typeof(ITuple)) && Context.Type != typeof(TransientTuple) - ? TransientTuple.FlattenTypes(Context.Type) + var tupleTypes = context.Type.IsAssignableTo(typeof(ITuple)) && context.Type != typeof(TransientTuple) + ? TransientTuple.FlattenTypes(context.Type) : null; if (tupleTypes is not null && tupleTypes.Length != tuple.InnerCodecs.Length) - throw new NoTypeConverterException($"Cannot determine inner types of the tuple {Context.Type}"); + throw new NoTypeConverterException($"Cannot determine inner types of the tuple {context.Type}"); - var type = typeof(object); - for (var i = 0; i != tuple.InnerCodecs.Length; i++) + await Task.WhenAll(tuple.InnerCodecs.Select(async (x, i) => { - if (tupleTypes != null) - type = tupleTypes[i]; - - var innerCodec = tuple.InnerCodecs[i]; - using var elementHandle = EnterNewContext(type); - - Visit(ref innerCodec); - - _logger.CodecVisitorMutatedCodec(Depth, tuple.InnerCodecs[i], innerCodec); - - tuple.InnerCodecs[i] = innerCodec; - } - - codec = tuple.GetCodecFor(_client.ProtocolProvider, GetContextualTypeForComplexCodec(tuple)); - _logger.CodecVisitorComplexCodecFlattened(Depth, tuple, codec, Context.Type); + var reference = new Ref(x); + await VisitCodecAsync(reference, context with + { + Type = tupleTypes is not null + ? tupleTypes[i] + : typeof(object) + }); + tuple.InnerCodecs[i] = reference.Value; + })); + + codec.Value = tuple.GetCodecFor(_client.ProtocolProvider, GetContextualTypeForComplexCodec(tuple, context)); } break; case CompilableWrappingCodec compilable: @@ -136,55 +159,43 @@ protected override void VisitCodec(ref ICodec codec) // visit the inner codec var tmp = compilable.InnerCodec; - var innerType = Context.InnerRealType || Context.IsDynamicResult - ? Context.Type - : Context.Type.GetWrappingType(); - - using (var innerHandle = EnterNewContext(innerType)) - { - VisitCodec(ref tmp); - - codec = compilable.Compile(_client.ProtocolProvider, Context.Type, tmp); + var innerType = context.InnerRealType || context.IsDynamicResult + ? context.Type + : context.Type.GetWrappingType(); - _logger.CodecVisitorCompiledCodec(Depth, compilable, codec, Context.Type); - } - - _logger.CodecVisitorMutatedCodec(Depth, tmp, codec); - - // visit the compiled codec - Visit(ref codec); + var reference = new Ref(compilable.InnerCodec); + await VisitCodecAsync(reference, context with { Type = innerType }); + codec.Value = compilable.Compile(_client.ProtocolProvider, innerType, reference.Value); } break; case IComplexCodec complex: { - codec = complex.GetCodecFor(_client.ProtocolProvider, GetContextualTypeForComplexCodec(complex)); - _logger.CodecVisitorComplexCodecFlattened(Depth, complex, codec, Context.Type); + codec.Value = complex.GetCodecFor(_client.ProtocolProvider, GetContextualTypeForComplexCodec(complex, context)); } break; case IRuntimeCodec runtime: { // check if the converter type is the same. // exit if they are: its the correct codec. - if (runtime.ConverterType == Context.Type) + if (runtime.ConverterType == context.Type) break; // ask the broker of the runtime codec for // the correct one. - codec = runtime.Broker.GetCodecFor(_client.ProtocolProvider, - GetContextualTypeForComplexCodec(runtime.Broker)); + codec.Value = runtime.Broker.GetCodecFor(_client.ProtocolProvider, + GetContextualTypeForComplexCodec(runtime.Broker, context)); - _logger.CodecVisitorRuntimeCodecBroker(Depth, runtime, runtime.Broker, codec, Context.Type); } break; } } - private Type GetContextualTypeForComplexCodec(IComplexCodec codec) + private Type GetContextualTypeForComplexCodec(IComplexCodec codec, TypeVisitorContext context) { // if theres a concrete type def supplied by the // user, return their requested type. - if (!Context.IsDynamicResult && !Context.InnerRealType) - return Context.Type; + if (!context.IsDynamicResult && !context.InnerRealType) + return context.Type; if (codec is ITemporalCodec temporal) { @@ -226,55 +237,22 @@ private Type GetContextualTypeForComplexCodec(IComplexCodec codec) // return out the current context type if we haven't // defined a way to change it. - return Context.Type; + return context.Type; } - private IDisposable EnterNewContext( - Type type, - string? name = null, - EdgeDBTypeDeserializeInfo? deserializer = null, - bool innerRealType = false) - { - var old = _frames.Count == 0 ? typeof(void) : Context.Type; - - _logger.CodecVisitorStackTransition(Depth, old, type); - - _frames.Push(new TypeResultFrame - { - Type = type, Name = name, Deserializer = deserializer, InnerRealType = innerRealType - }); - - - return new FrameHandle(_logger, Depth, _frames); - } - - private struct TypeResultFrame + private record TypeVisitorContext(Type Type) { public bool IsDynamicResult => Type == typeof(object); - public readonly Type Type { get; init; } public string? Name { get; set; } public EdgeDBTypeDeserializeInfo? Deserializer { get; set; } public bool InnerRealType { get; set; } - } - private readonly struct FrameHandle : IDisposable - { - private readonly Stack _stack; - private readonly ILogger _logger; - private readonly string _depth; - - - public FrameHandle(ILogger logger, string depth, Stack stack) - { - _depth = depth; - _logger = logger; - _stack = stack; - } + public int Depth { get; init; } - public void Dispose() => - _logger.CodecVisitorFramePopped(_depth == string.Empty ? string.Empty : $"{_depth[..^2]} ", - _stack.Pop().Type); + public string FormattedDepth + => "".PadRight(Depth, '>') + (Depth > 0 ? " " : ""); } + } diff --git a/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs b/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs index 7cd79c51..b7c35316 100644 --- a/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs +++ b/src/EdgeDB.Net.Driver/Binary/Protocol/V1.0/V1ProtocolProvider.cs @@ -70,13 +70,30 @@ private ILogger Logger _ => null }; + private ReadOnlyMemory SerializeArguments( + IDictionary arguments, + IArgumentCodec codec, + ArgumentVisitor visitor) + { + var writer = new PacketWriter(); + try + { + codec.SerializeArguments( + ref writer, + arguments, + new ArgumentCodecContext(visitor.VisitedChildCodecs, _client) + ); + return writer.GetBytes(); + } + finally + { + writer.Dispose(); + } + } + public virtual async Task ExecuteQueryAsync(QueryParameters queryParameters, ParseResult parseResult, CancellationToken token) { - if (parseResult.InCodecInfo.Codec is not IArgumentCodec argumentCodec) - throw new MissingCodecException( - $"Cannot encode arguments, {parseResult.InCodecInfo.Codec} is not a registered argument codec"); - ErrorResponse? error = null; var executeSuccess = false; var gotStateDescriptor = false; @@ -85,6 +102,22 @@ public virtual async Task ExecuteQueryAsync(QueryParameters query var stateBuf = parseResult.StateData; + ReadOnlyMemory argumentBuffer = Array.Empty(); + + if (queryParameters.Arguments is not null) + { + var inCodec = new Ref(parseResult.InCodecInfo.Codec); + + var argumentVisitor = new ArgumentVisitor(_client, queryParameters.Arguments); + await argumentVisitor.VisitAsync(inCodec); + + if (inCodec.Value is not IArgumentCodec argumentCodec) + throw new MissingCodecException( + $"Cannot encode arguments, {parseResult.InCodecInfo.Codec} is not a registered argument codec"); + + argumentBuffer = SerializeArguments(queryParameters.Arguments, argumentCodec, argumentVisitor); + } + do { await foreach (var result in Duplexer.DuplexAndSyncAsync(new Execute @@ -98,7 +131,7 @@ public virtual async Task ExecuteQueryAsync(QueryParameters query StateData = stateBuf, ImplicitTypeNames = queryParameters.ImplicitTypeNames, // used for type builder ImplicitTypeIds = _client.ClientConfig.ImplicitTypeIds, - Arguments = argumentCodec.SerializeArguments(_client, queryParameters.Arguments), + Arguments = argumentBuffer, ImplicitLimit = _client.ClientConfig.ImplicitLimit, InputTypeDescriptorId = parseResult.InCodecInfo.Id, OutputTypeDescriptorId = parseResult.OutCodecInfo.Id diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 3aee1709..72f7989d 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -283,10 +283,22 @@ public override async Task ExecuteAsync(string query, IDictionary(this, codec, in result.Data[i]); - array[i] = obj; + for (var i = 0; i != result.Data.Length; i++) + { + var obj = await ObjectBuilder.BuildResultAsync(this, codec, result.Data[i]); + array[i] = obj; + } + } + else + { + await Task.WhenAll( + result.Data.Select(async (x, i) => + { + array[i] = await ObjectBuilder.BuildResultAsync(this, codec, x); + }) + ); } return array.ToImmutableArray(); @@ -323,7 +335,7 @@ public override async Task ExecuteAsync(string query, IDictionary(this, result.OutCodecInfo.Codec, in result.Data[0]); + : await ObjectBuilder.BuildResultAsync(this, result.OutCodecInfo.Codec, result.Data[0]); } /// @@ -359,7 +371,8 @@ public override async Task QueryRequiredSingleAsync(string que return result.Data.Length != 1 ? throw new MissingRequiredException() - : ObjectBuilder.BuildResult(this, result.OutCodecInfo.Codec, in result.Data[0])!; + : await ObjectBuilder.BuildResultAsync(this, result.OutCodecInfo.Codec, result.Data[0]) + ?? throw new ResultCardinalityMismatchException(Cardinality.One, Cardinality.NoResult); } /// diff --git a/src/EdgeDB.Net.Driver/Extensions/CodecExtensions.cs b/src/EdgeDB.Net.Driver/Extensions/CodecExtensions.cs index 7e3a1338..4a8be442 100644 --- a/src/EdgeDB.Net.Driver/Extensions/CodecExtensions.cs +++ b/src/EdgeDB.Net.Driver/Extensions/CodecExtensions.cs @@ -5,18 +5,6 @@ namespace EdgeDB; internal static class CodecExtensions { - #region IArgumentCodec - - public static ReadOnlyMemory SerializeArguments(this IArgumentCodec codec, EdgeDBBinaryClient client, - object? value) - { - var writer = new PacketWriter(); - codec.SerializeArguments(ref writer, value, client.CodecContext); - return writer.GetBytes(); - } - - #endregion - #region ICodec public static object? Deserialize(this ICodec codec, EdgeDBBinaryClient client, in ReadOnlySpan buffer) diff --git a/src/EdgeDB.Net.Driver/Log.cs b/src/EdgeDB.Net.Driver/Log.cs index bc36d9d3..dcb28f77 100644 --- a/src/EdgeDB.Net.Driver/Log.cs +++ b/src/EdgeDB.Net.Driver/Log.cs @@ -168,9 +168,9 @@ public static partial void CodecVisitorRuntimeCodecBroker(this ILogger logger, s [LoggerMessage( 24, - LogLevel.Debug, - "{Depth}Frame for {Type} exited")] - public static partial void CodecVisitorFramePopped(this ILogger logger, string depth, Type type); + LogLevel.Trace, + "Codec visitor {Visitor} took {Time}ms for the following tree:\n{CodecTree}")] + public static partial void CodecVisitorTimingTrace(this ILogger logger, CodecVisitor visitor, double time, string codecTree); [LoggerMessage( 25, @@ -222,4 +222,10 @@ public static partial void BeginProtocolNegotiation(this ILogger logget, Protoco LogLevel.Debug, "Codec visited in preperation for deserialization: \n{Codec}")] public static partial void ObjectDeserializationPrep(this ILogger logger, string codec); + + [LoggerMessage( + 33, + LogLevel.Trace, + "Codec tree information:\n{CodecTree}")] + public static partial void CodecTree(this ILogger logger, string codecTree); } diff --git a/src/EdgeDB.Net.Driver/Utils/Ref.cs b/src/EdgeDB.Net.Driver/Utils/Ref.cs new file mode 100644 index 00000000..aed77826 --- /dev/null +++ b/src/EdgeDB.Net.Driver/Utils/Ref.cs @@ -0,0 +1,10 @@ +namespace EdgeDB.Utils; + +internal sealed class Ref(T value) +where T : class +{ + public T Value { get; set; } = value; + + public static implicit operator Ref(T value) => new(value); + public static implicit operator T(Ref reference) => reference.Value; +} diff --git a/tests/EdgeDB.Tests.Benchmarks/CodecVisitorBenchmarks.cs b/tests/EdgeDB.Tests.Benchmarks/CodecVisitorBenchmarks.cs new file mode 100644 index 00000000..e1b93026 --- /dev/null +++ b/tests/EdgeDB.Tests.Benchmarks/CodecVisitorBenchmarks.cs @@ -0,0 +1,16 @@ +namespace EdgeDB.Tests.Benchmarks; + +public class CodecVisitorBenchmarks +{ + + + public async Task BenchmarkArgumentVisitor() + { + + } + + public async Task BenchmarkResultVisitor() + { + + } +}