From 6f604d5237b0c9e65a51ca37c70bc6a650250e45 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 10:37:08 +0100 Subject: [PATCH 01/11] add base serialization2 tests --- .../Api/Operations/Operations.Serialize.cs | 6 +- .../Serialisation/BaseObjectDeserializerV2.cs | 0 .../Serialisation/SpeckleObjectSerializer2.cs | 547 ++++++++++++++++++ .../Api/Operations/SerializationTests2.cs | 276 +++++++++ .../SerializerNonBreakingChanges2.cs | 311 ++++++++++ .../Serialisation/SimpleRoundTripTests2.cs | 55 ++ 6 files changed, 1194 insertions(+), 1 deletion(-) delete mode 100644 src/Speckle.Sdk/Serialisation/BaseObjectDeserializerV2.cs create mode 100644 src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests2.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges2.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests2.cs diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs index 1c2e3cd3..ebc16a64 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs @@ -23,7 +23,11 @@ public string Serialize(Base value, CancellationToken cancellationToken = defaul var serializer = new SpeckleObjectSerializer { CancellationToken = cancellationToken }; return serializer.Serialize(value); } - + public string Serialize2(Base value, CancellationToken cancellationToken = default) + { + var serializer = new SpeckleObjectSerializer2 { CancellationToken = cancellationToken }; + return serializer.Serialize(value); + } /// /// Note: if you want to pull an object from a Speckle Transport or Server, /// please use diff --git a/src/Speckle.Sdk/Serialisation/BaseObjectDeserializerV2.cs b/src/Speckle.Sdk/Serialisation/BaseObjectDeserializerV2.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs new file mode 100644 index 00000000..014b7b1e --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs @@ -0,0 +1,547 @@ +using System.Collections; +using System.Diagnostics.Contracts; +using System.Drawing; +using System.Globalization; +using System.Reflection; +using Speckle.DoubleNumerics; +using Speckle.Newtonsoft.Json; +using Speckle.Sdk.Common; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Models; +using Speckle.Sdk.Transports; +using Constants = Speckle.Sdk.Helpers.Constants; + +namespace Speckle.Sdk.Serialisation; + +public class SpeckleObjectSerializer2 +{ + private volatile bool _isBusy; + private List> _parentClosures = new(); + private HashSet _parentObjects = new(); + private readonly Dictionary> _typedPropertiesCache = new(); + private readonly Action? _onProgressAction; + + private readonly bool _trackDetachedChildren; + private int _serializedCount; + + /// + /// Keeps track of all detached children created during serialisation that have an applicationId (provided this serializer instance has been told to track detached children). + /// This is currently used to cache previously converted objects and avoid their conversion if they haven't changed. See the DUI3 send bindings in rhino or another host app. + /// + public Dictionary ObjectReferences { get; } = new(); + + /// The sync transport. This transport will be used synchronously. + public IReadOnlyCollection WriteTransports { get; } + + public CancellationToken CancellationToken { get; set; } + + public SpeckleObjectSerializer2() + : this(Array.Empty()) { } + + /// + /// Creates a new Serializer instance. + /// + /// The transports detached children should be persisted to. + /// Used to track progress. + /// Whether to store all detachable objects while serializing. They can be retrieved via post serialization. + /// + public SpeckleObjectSerializer2( + IReadOnlyCollection writeTransports, + Action? onProgressAction = null, + bool trackDetachedChildren = false, + CancellationToken cancellationToken = default + ) + { + WriteTransports = writeTransports; + _onProgressAction = onProgressAction; + CancellationToken = cancellationToken; + _trackDetachedChildren = trackDetachedChildren; + } + + /// The object to serialize + /// The serialized JSON + /// The serializer is busy (already serializing an object) + /// Failed to save object in one or more + /// Failed to extract (pre-serialize) properties from the + /// One or more 's cancellation token requested cancel + public string Serialize(Base baseObj) + { + if (_isBusy) + { + throw new InvalidOperationException( + "A serializer instance can serialize only 1 object at a time. Consider creating multiple serializer instances" + ); + } + + try + { + _isBusy = true; + try + { + var result = SerializeBase(baseObj, true).NotNull(); + StoreObject(result.Id.NotNull(), result.Json); + return result.Json; + } + catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) + { + throw new SpeckleSerializeException($"Failed to extract (pre-serialize) properties from the {baseObj}", ex); + } + } + finally + { + _parentClosures = new List>(); // cleanup in case of exceptions + _parentObjects = new HashSet(); + _isBusy = false; + } + } + + // `Preserialize` means transforming all objects into the final form that will appear in json, with basic .net objects + // (primitives, lists and dictionaries with string keys) + private void SerializeProperty( + object? obj, + JsonWriter writer, + bool computeClosures = false, + PropertyAttributeInfo inheritedDetachInfo = default + ) + { + CancellationToken.ThrowIfCancellationRequested(); + + if (obj == null) + { + writer.WriteNull(); + return; + } + + if (obj.GetType().IsPrimitive || obj is string) + { + writer.WriteValue(obj); + return; + } + + switch (obj) + { + // Start with object references so they're not captured by the Base class case below + // Note: this change was needed as we've made the ObjectReference type inherit from Base for + // the purpose of the "do not convert unchanged previously converted objects" POC. + case ObjectReference r: + Dictionary ret = + new() + { + ["speckle_type"] = r.speckle_type, + ["referencedId"] = r.referencedId, + ["__closure"] = r.closure + }; + if (r.closure is not null) + { + foreach (var kvp in r.closure) + { + UpdateParentClosures(kvp.Key); + } + } + UpdateParentClosures(r.referencedId); + SerializeProperty(ret, writer); + break; + case Base b: + var result = SerializeBase(b, computeClosures, inheritedDetachInfo); + if (result is not null) + { + writer.WriteRawValue(result.Json); + } + else + { + writer.WriteNull(); + } + break; + case IDictionary d: + { + writer.WriteStartObject(); + + foreach (DictionaryEntry kvp in d) + { + if (kvp.Key is not string key) + { + throw new ArgumentException( + "Serializing dictionaries that are not string based keys is not supported", + nameof(obj) + ); + } + + writer.WritePropertyName(key); + SerializeProperty(kvp.Value, writer, inheritedDetachInfo: inheritedDetachInfo); + } + writer.WriteEndObject(); + } + break; + case ICollection e: + { + writer.WriteStartArray(); + foreach (object? element in e) + { + SerializeProperty(element, writer, inheritedDetachInfo: inheritedDetachInfo); + } + writer.WriteEndArray(); + } + break; + case Enum: + writer.WriteValue((int)obj); + break; + // Support for simple types + case Guid g: + writer.WriteValue(g.ToString()); + break; + case Color c: + writer.WriteValue(c.ToArgb()); + break; + case DateTime t: + writer.WriteValue(t.ToString("o", CultureInfo.InvariantCulture)); + break; + case Matrix4x4 md: + writer.WriteStartArray(); + + writer.WriteValue(md.M11); + writer.WriteValue(md.M12); + writer.WriteValue(md.M13); + writer.WriteValue(md.M14); + writer.WriteValue(md.M21); + writer.WriteValue(md.M22); + writer.WriteValue(md.M23); + writer.WriteValue(md.M24); + writer.WriteValue(md.M31); + writer.WriteValue(md.M32); + writer.WriteValue(md.M33); + writer.WriteValue(md.M34); + writer.WriteValue(md.M41); + writer.WriteValue(md.M42); + writer.WriteValue(md.M43); + writer.WriteValue(md.M44); + writer.WriteEndArray(); + break; + //BACKWARDS COMPATIBILITY: matrix4x4 changed from System.Numerics float to System.DoubleNumerics double in release 2.16 + case System.Numerics.Matrix4x4: + throw new ArgumentException("Please use Speckle.DoubleNumerics.Matrix4x4 instead", nameof(obj)); + default: + throw new ArgumentException($"Unsupported value in serialization: {obj.GetType()}", nameof(obj)); + } + } + + internal SerializationResult? SerializeBase( + Base baseObj, + bool computeClosures = false, + PropertyAttributeInfo inheritedDetachInfo = default + ) + { + // handle circular references + bool alreadySerialized = !_parentObjects.Add(baseObj); + if (alreadySerialized) + { + return null; + } + + Dictionary closure = new(); + if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) + { + _parentClosures.Add(closure); + } + + using var writer = new StringWriter(); + using var jsonWriter = new JsonTextWriter(writer); + string id = SerializeBaseObject(baseObj, jsonWriter, closure); + var json = writer.ToString(); + + if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) + { + _parentClosures.RemoveAt(_parentClosures.Count - 1); + } + + _parentObjects.Remove(baseObj); + + if (baseObj is Blob myBlob) + { + StoreBlob(myBlob); + UpdateParentClosures($"blob:{id}"); + return new(json, id); + } + + if (inheritedDetachInfo.IsDetachable && WriteTransports.Count > 0) + { + StoreObject(id, json); + + ObjectReference objRef = new() { referencedId = id }; + using var writer2 = new StringWriter(); + using var jsonWriter2 = new JsonTextWriter(writer2); + SerializeProperty(objRef, jsonWriter2); + var json2 = writer2.ToString(); + UpdateParentClosures(id); + + _onProgressAction?.Invoke(new(ProgressEvent.SerializeObject, ++_serializedCount, null)); + + // add to obj refs to return + if (baseObj.applicationId != null && _trackDetachedChildren) // && baseObj is not DataChunk && baseObj is not Abstract) // not needed, as data chunks will never have application ids, and abstract objs are not really used. + { + ObjectReferences[baseObj.applicationId] = new ObjectReference() + { + referencedId = id, + applicationId = baseObj.applicationId, + closure = closure + }; + } + return new(json2, null); + } + return new(json, id); + } + + private Dictionary ExtractAllProperties(Base baseObj) + { + IReadOnlyList<(PropertyInfo, PropertyAttributeInfo)> typedProperties = GetTypedPropertiesWithCache(baseObj); + IReadOnlyCollection dynamicProperties = baseObj.DynamicPropertyKeys; + + // propertyName -> (originalValue, isDetachable, isChunkable, chunkSize) + Dictionary allProperties = + new(typedProperties.Count + dynamicProperties.Count); + + // Construct `allProperties`: Add typed properties + foreach ((PropertyInfo propertyInfo, PropertyAttributeInfo detachInfo) in typedProperties) + { + object? baseValue = propertyInfo.GetValue(baseObj); + allProperties[propertyInfo.Name] = (baseValue, detachInfo); + } + + // Construct `allProperties`: Add dynamic properties + foreach (string propName in dynamicProperties) + { + if (propName.StartsWith("__")) + { + continue; + } + + object? baseValue = baseObj[propName]; +#if NETSTANDARD2_0 + bool isDetachable = propName.StartsWith("@"); +#else + bool isDetachable = propName.StartsWith('@'); +#endif + bool isChunkable = false; + int chunkSize = 1000; + + if (Constants.ChunkPropertyNameRegex.IsMatch(propName)) + { + var match = Constants.ChunkPropertyNameRegex.Match(propName); + isChunkable = int.TryParse(match.Groups[^1].Value, out chunkSize); + } + allProperties[propName] = (baseValue, new PropertyAttributeInfo(isDetachable, isChunkable, chunkSize, null)); + } + + return allProperties; + } + + private string SerializeBaseObject(Base baseObj, JsonWriter writer, IReadOnlyDictionary closure) + { + var allProperties = ExtractAllProperties(baseObj); + + if (baseObj is not Blob) + { + writer = new SerializerIdWriter(writer); + } + + writer.WriteStartObject(); + // Convert all properties + foreach (var prop in allProperties) + { + if (prop.Value.info.JsonPropertyInfo is { NullValueHandling: NullValueHandling.Ignore }) + { + continue; + } + + writer.WritePropertyName(prop.Key); + SerializeProperty(prop.Value.value, writer, prop.Value.info); + } + + string id; + if (writer is SerializerIdWriter serializerIdWriter) + { + (var json, writer) = serializerIdWriter.FinishIdWriter(); + id = ComputeId(json); + } + else + { + id = ((Blob)baseObj).id; + } + writer.WritePropertyName("id"); + writer.WriteValue(id); + baseObj.id = id; + + if (closure.Count > 0) + { + writer.WritePropertyName("__closure"); + writer.WriteStartObject(); + foreach (var c in closure) + { + writer.WritePropertyName(c.Key); + writer.WriteValue(c.Value); + } + writer.WriteEndObject(); + } + + writer.WriteEndObject(); + return id; + } + + private void SerializeProperty(object? baseValue, JsonWriter jsonWriter, PropertyAttributeInfo detachInfo) + { + // If there are no WriteTransports, keep everything attached. + if (WriteTransports.Count == 0) + { + SerializeProperty(baseValue, jsonWriter, inheritedDetachInfo: detachInfo); + return; + } + + if (baseValue is IEnumerable chunkableCollection && detachInfo.IsChunkable) + { + List chunks = new(); + DataChunk crtChunk = new() { data = new List(detachInfo.ChunkSize) }; + + foreach (object element in chunkableCollection) + { + crtChunk.data.Add(element); + if (crtChunk.data.Count >= detachInfo.ChunkSize) + { + chunks.Add(crtChunk); + crtChunk = new DataChunk { data = new List(detachInfo.ChunkSize) }; + } + } + + if (crtChunk.data.Count > 0) + { + chunks.Add(crtChunk); + } + SerializeProperty(chunks, jsonWriter, inheritedDetachInfo: new PropertyAttributeInfo(true, false, 0, null)); + return; + } + + SerializeProperty(baseValue, jsonWriter, inheritedDetachInfo: detachInfo); + } + + private void UpdateParentClosures(string objectId) + { + for (int parentLevel = 0; parentLevel < _parentClosures.Count; parentLevel++) + { + int childDepth = _parentClosures.Count - parentLevel; + if (!_parentClosures[parentLevel].TryGetValue(objectId, out int currentValue)) + { + currentValue = childDepth; + } + + _parentClosures[parentLevel][objectId] = Math.Min(currentValue, childDepth); + } + } + + [Pure] + private static string ComputeId(string serialized) + { +#if NET6_0_OR_GREATER + string hash = Crypt.Sha256(serialized.AsSpan(), length: HashUtility.HASH_LENGTH); +#else + string hash = Crypt.Sha256(serialized, length: HashUtility.HASH_LENGTH); +#endif + return hash; + } + + private void StoreObject(string objectId, string objectJson) + { + foreach (var transport in WriteTransports) + { + transport.SaveObject(objectId, objectJson); + } + } + + private void StoreBlob(Blob obj) + { + bool hasBlobTransport = false; + + foreach (var transport in WriteTransports) + { + if (transport is IBlobCapableTransport blobTransport) + { + hasBlobTransport = true; + blobTransport.SaveBlob(obj); + } + } + if (!hasBlobTransport) + { + throw new InvalidOperationException( + "Object tree contains a Blob (file), but the serializer has no blob saving capable transports." + ); + } + } + + // (propertyInfo, isDetachable, isChunkable, chunkSize, JsonPropertyAttribute) + private IReadOnlyList<(PropertyInfo, PropertyAttributeInfo)> GetTypedPropertiesWithCache(Base baseObj) + { + Type type = baseObj.GetType(); + + if ( + _typedPropertiesCache.TryGetValue( + type.FullName.NotNull(), + out List<(PropertyInfo, PropertyAttributeInfo)>? cached + ) + ) + { + return cached; + } + + var typedProperties = baseObj.GetInstanceMembers().ToList(); + List<(PropertyInfo, PropertyAttributeInfo)> ret = new(typedProperties.Count); + + foreach (PropertyInfo typedProperty in typedProperties) + { + if (typedProperty.Name.StartsWith("__") || typedProperty.Name == "id") + { + continue; + } + + bool jsonIgnore = typedProperty.IsDefined(typeof(JsonIgnoreAttribute), false); + if (jsonIgnore) + { + continue; + } + + _ = typedProperty.GetValue(baseObj); + + List detachableAttributes = typedProperty + .GetCustomAttributes(true) + .ToList(); + List chunkableAttributes = typedProperty + .GetCustomAttributes(true) + .ToList(); + bool isDetachable = detachableAttributes.Count > 0 && detachableAttributes[0].Detachable; + bool isChunkable = chunkableAttributes.Count > 0; + int chunkSize = isChunkable ? chunkableAttributes[0].MaxObjCountPerChunk : 1000; + JsonPropertyAttribute? jsonPropertyAttribute = typedProperty.GetCustomAttribute(); + ret.Add((typedProperty, new PropertyAttributeInfo(isDetachable, isChunkable, chunkSize, jsonPropertyAttribute))); + } + + _typedPropertiesCache[type.FullName] = ret; + return ret; + } + + internal readonly struct PropertyAttributeInfo + { + public PropertyAttributeInfo( + bool isDetachable, + bool isChunkable, + int chunkSize, + JsonPropertyAttribute? jsonPropertyAttribute + ) + { + IsDetachable = isDetachable || isChunkable; + IsChunkable = isChunkable; + ChunkSize = chunkSize; + JsonPropertyInfo = jsonPropertyAttribute; + } + + public readonly bool IsDetachable; + public readonly bool IsChunkable; + public readonly int ChunkSize; + public readonly JsonPropertyAttribute? JsonPropertyInfo; + } +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests2.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests2.cs new file mode 100644 index 00000000..9dfddb3a --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests2.cs @@ -0,0 +1,276 @@ +using System.Drawing; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Speckle.Sdk.Api; +using Speckle.Sdk.Api.GraphQL.Models; +using Speckle.Sdk.Credentials; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Tests.Unit.Host; +using Point = Speckle.Sdk.Tests.Unit.Host.Point; + +namespace Speckle.Sdk.Tests.Unit.Api.Operations; + +[TestFixture] +[TestOf(typeof(Sdk.Api.Operations))] +public class ObjectSerialization2 +{ + private IOperations _operations; + + [SetUp] + public void Setup() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly, typeof(ColorMock2).Assembly); + var serviceProvider = TestServiceSetup.GetServiceProvider(); + _operations = serviceProvider.GetRequiredService(); + } + + [Test] + public async Task IgnoreCircularReferences() + { + var pt = new Point(1, 2, 3); + pt["circle"] = pt; + + var test = _operations.Serialize2(pt); + + var result = await _operations.DeserializeAsync(test); + var circle = result["circle"]; + Assert.That(circle, Is.Null); + } + + [Test] + public async Task InterfacePropHandling() + { + Line tail = new() { Start = new Point(0, 0, 0), End = new Point(42, 42, 42) }; + PolygonalFeline cat = new() { Tail = tail }; + + for (int i = 0; i < 10; i++) + { + cat.Claws[$"Claw number {i}"] = new Line + { + Start = new Point(i, i, i), + End = new Point(i + 3.14, i + 3.14, i + 3.14) + }; + + if (i % 2 == 0) + { + cat.Whiskers.Add( + new Line { Start = new Point(i / 2, i / 2, i / 2), End = new Point(i + 3.14, i + 3.14, i + 3.14) } + ); + } + else + { + var brokenWhisker = new Polyline(); + brokenWhisker.Points.Add(new Point(-i, 0, 0)); + brokenWhisker.Points.Add(new Point(0, 0, 0)); + brokenWhisker.Points.Add(new Point(i, 0, 0)); + cat.Whiskers.Add(brokenWhisker); + } + + cat.Fur[i] = new Line { Start = new Point(i, i, i), End = new Point(i + 3.14, i + 3.14, i + 3.14) }; + } + + var result = _operations.Serialize2(cat); + + var deserialisedFeline = await _operations.DeserializeAsync(result); + + Assert.That(deserialisedFeline.GetId(), Is.EqualTo(cat.GetId())); // If we're getting the same hash... we're probably fine! + } + + [Test] + public async Task InheritanceTests() + { + var superPoint = new SuperPoint + { + X = 10, + Y = 10, + Z = 10, + W = 42 + }; + + var str = _operations.Serialize2(superPoint); + var sstr = await _operations.DeserializeAsync(str); + + Assert.That(sstr.speckle_type, Is.EqualTo(superPoint.speckle_type)); + } + + [Test] + public async Task ListDynamicProp() + { + var point = new Point(); + var test = new List(); + + for (var i = 0; i < 100; i++) + { + test.Add(new SuperPoint { W = i }); + } + + point["test"] = test; + + var str = _operations.Serialize2(point); + var dsrls = await _operations.DeserializeAsync(str); + + var list = dsrls["test"] as List; // NOTE: on dynamically added lists, we cannot infer the inner type and we always fall back to a generic list. + Assert.That(list, Has.Count.EqualTo(100)); + } + + [Test] + public async Task ChunkSerialisation() + { + var baseBasedChunk = new DataChunk() { data = new() }; + for (var i = 0; i < 200; i++) + { + baseBasedChunk.data.Add(new SuperPoint { W = i }); + } + + var stringBasedChunk = new DataChunk() { data = new() }; + for (var i = 0; i < 200; i++) + { + stringBasedChunk.data.Add(i + "_hai"); + } + + var doubleBasedChunk = new DataChunk() { data = new() }; + for (var i = 0; i < 200; i++) + { + doubleBasedChunk.data.Add(i + 0.33); + } + + var baseChunkString = _operations.Serialize2(baseBasedChunk); + var stringChunkString = _operations.Serialize2(stringBasedChunk); + var doubleChunkString = _operations.Serialize2(doubleBasedChunk); + + var baseChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(baseChunkString); + var stringChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(stringChunkString); + var doubleChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(doubleChunkString); + + Assert.That(baseChunkDeserialised.data, Has.Count.EqualTo(baseBasedChunk.data.Count)); + Assert.That(stringChunkDeserialised.data, Has.Count.EqualTo(stringBasedChunk.data.Count)); + Assert.That(doubleChunkDeserialised.data, Has.Count.EqualTo(doubleBasedChunk.data.Count)); + } + + [Test] + public async Task ObjectWithChunksSerialisation() + { + const int MAX_NUM = 2020; + var mesh = new FakeMesh { ArrayOfDoubles = new double[MAX_NUM], ArrayOfLegs = new TableLeg[MAX_NUM] }; + + var customChunk = new List(); + var defaultChunk = new List(); + + for (int i = 0; i < MAX_NUM; i++) + { + mesh.Vertices.Add(i / 2); + customChunk.Add(i / 2); + defaultChunk.Add(i / 2); + mesh.Tables.Add(new Tabletop { length = 2000 }); + mesh.ArrayOfDoubles[i] = i * 3.3; + mesh.ArrayOfLegs[i] = new TableLeg { height = 2 + i }; + } + + mesh["@(800)CustomChunk"] = customChunk; + mesh["@()DefaultChunk"] = defaultChunk; + + var serialised = _operations.Serialize2(mesh); + var deserialised = await _operations.DeserializeAsync(serialised); + + Assert.That(mesh.GetId(), Is.EqualTo(deserialised.GetId())); + } + + [Test] + public void EmptyListSerialisationTests() + { + // NOTE: expected behaviour is that empty lists should serialize as empty lists. Don't ask why, it's complicated. + // Regarding chunkable empty lists, to prevent empty chunks, the expected behaviour is to have an empty lists, with no chunks inside. + var test = new Base(); + + test["@(5)emptyChunks"] = new List(); + test["emptyList"] = new List(); + test["@emptyDetachableList"] = new List(); + + // Note: nested empty lists should be preserved. + test["nestedList"] = new List { new List { new List() } }; + test["@nestedDetachableList"] = new List { new List { new List() } }; + + var serialised = _operations.Serialize2(test); + var isCorrect = + serialised.Contains("\"@(5)emptyChunks\":[]") + && serialised.Contains("\"emptyList\":[]") + && serialised.Contains("\"@emptyDetachableList\":[]") + && serialised.Contains("\"nestedList\":[[[]]]") + && serialised.Contains("\"@nestedDetachableList\":[[[]]]"); + + Assert.That(isCorrect, Is.EqualTo(true)); + } + + [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+DateMock2")] + private class DateMock2 : Base + { + public DateTime TestField { get; set; } + } + + [Test] + public async Task DateSerialisation() + { + var date = new DateTime(2020, 1, 14); + var mockBase = new DateMock2 { TestField = date }; + + var result = _operations.Serialize2(mockBase); + var test = (DateMock2)await _operations.DeserializeAsync(result); + + Assert.That(test.TestField, Is.EqualTo(date)); + } + + [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+GUIDMock2")] + private class GUIDMock2 : Base + { + public Guid TestField { get; set; } + } + + [Test] + public async Task GuidSerialisation() + { + var guid = Guid.NewGuid(); + var mockBase = new GUIDMock2 { TestField = guid }; + + var result = _operations.Serialize2(mockBase); + var test = (GUIDMock2)await _operations.DeserializeAsync(result); + + Assert.That(test.TestField, Is.EqualTo(guid)); + } + + [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+ColorMock2")] + private class ColorMock2 : Base + { + public Color TestField { get; set; } + } + + [Test] + public async Task ColorSerialisation() + { + var color = Color.FromArgb(255, 4, 126, 251); + var mockBase = new ColorMock2 { TestField = color }; + + var result = _operations.Serialize2(mockBase); + var test = (ColorMock2)await _operations.DeserializeAsync(result); + + Assert.That(test.TestField, Is.EqualTo(color)); + } + + [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+StringDateTimeRegressionMock2")] + private class StringDateTimeRegressionMock2 : Base + { + public string TestField { get; set; } + } + + [Test] + public async Task StringDateTimeRegression() + { + var mockBase = new StringDateTimeRegressionMock2 { TestField = "2021-11-12T11:32:01" }; + + var result = _operations.Serialize2(mockBase); + var test = (StringDateTimeRegressionMock2)await _operations.DeserializeAsync(result); + + Assert.That(test.TestField, Is.EqualTo(mockBase.TestField)); + } +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges2.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges2.cs new file mode 100644 index 00000000..302f3f53 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges2.cs @@ -0,0 +1,311 @@ +using System.Drawing; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Shouldly; +using Speckle.Sdk.Api; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation; +using Matrix4x4 = Speckle.DoubleNumerics.Matrix4x4; + +namespace Speckle.Sdk.Tests.Unit.Serialisation; + +/// +/// Test fixture that documents what property typing changes maintain backwards/cross/forwards compatibility, and are "non-breaking" changes. +/// This doesn't guarantee things work this way for SpecklePy +/// Nor does it encompass other tricks (like deserialize callback, or computed json ignored properties) +/// +[TestFixture] +[Description("For certain types, changing property from one type to another should be implicitly backwards compatible")] +public class SerializerNonBreakingChanges2 : PrimitiveTestFixture2 +{ + private IOperations _operations; + + [SetUp] + public void Setup() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(StringValueMock).Assembly); + var serviceProvider = TestServiceSetup.GetServiceProvider(); + _operations = serviceProvider.GetRequiredService(); + } + + [Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases))] + public async Task IntToColor(int argb) + { + var from = new IntValueMock { value = argb }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value.ToArgb(), Is.EqualTo(argb)); + } + + [Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases))] + public async Task ColorToInt(int argb) + { + var from = new ColorValueMock { value = Color.FromArgb(argb) }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(argb)); + } + + [ + Test, + TestCaseSource(nameof(Int8TestCases)), + TestCaseSource(nameof(Int32TestCases)), + TestCaseSource(nameof(Int64TestCases)) + ] + public async Task IntToDouble(long testCase) + { + var from = new IntValueMock { value = testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(testCase)); + } + + [Test] + public async Task NullToInt() + { + var from = new ObjectValueMock { value = null }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(default(int))); + } + + [Test] + public async Task NullToDouble() + { + var from = new ObjectValueMock { value = null }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(default(double))); + } + + // IMPORTANT!!: This test mimics large numbers that we sometimes see from python + // This is behaviour our deserializer has, but not necessarily commited to keeping + // Numbers outside the range of a Long are not officially supported + [Test] + [TestCaseSource(nameof(UInt64TestCases))] + [DefaultFloatingPointTolerance(2048)] + public async Task UIntToDouble(ulong testCase) + { + var from = new UIntValueMock { value = testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(testCase)); + } + + [ + Test, + TestCaseSource(nameof(Int8TestCases)), + TestCaseSource(nameof(Int32TestCases)), + TestCaseSource(nameof(Int64TestCases)) + ] + public async Task IntToString(long testCase) + { + var from = new IntValueMock { value = testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(testCase.ToString())); + } + + private static readonly double[][] s_arrayTestCases = + { + Array.Empty(), + new double[] { 0, 1, int.MaxValue, int.MinValue }, + new[] { default, double.Epsilon, double.MaxValue, double.MinValue } + }; + + [Test, TestCaseSource(nameof(s_arrayTestCases))] + public async Task ArrayToList(double[] testCase) + { + var from = new ArrayDoubleValueMock { value = testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EquivalentTo(testCase)); + } + + [Test, TestCaseSource(nameof(s_arrayTestCases))] + public async Task ListToArray(double[] testCase) + { + var from = new ListDoubleValueMock { value = testCase.ToList() }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EquivalentTo(testCase)); + } + + [Test, TestCaseSource(nameof(s_arrayTestCases))] + public async Task ListToIList(double[] testCase) + { + var from = new ListDoubleValueMock { value = testCase.ToList() }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EquivalentTo(testCase)); + } + + [Test, TestCaseSource(nameof(s_arrayTestCases))] + public async Task ListToIReadOnlyList(double[] testCase) + { + var from = new ListDoubleValueMock { value = testCase.ToList() }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EquivalentTo(testCase)); + } + + [Test, TestCaseSource(nameof(s_arrayTestCases))] + public async Task IListToList(double[] testCase) + { + var from = new IListDoubleValueMock { value = testCase.ToList() }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EquivalentTo(testCase)); + } + + [Test, TestCaseSource(nameof(s_arrayTestCases))] + public async Task IReadOnlyListToList(double[] testCase) + { + var from = new IReadOnlyListDoubleValueMock { value = testCase.ToList() }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EquivalentTo(testCase)); + } + + [Test, TestCaseSource(nameof(MyEnums))] + public async Task EnumToInt(MyEnum testCase) + { + var from = new EnumValueMock { value = testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo((int)testCase)); + } + + [Test, TestCaseSource(nameof(MyEnums))] + public async Task IntToEnum(MyEnum testCase) + { + var from = new IntValueMock { value = (int)testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(testCase)); + } + + [Test] + [TestCaseSource(nameof(Float64TestCases))] + [TestCaseSource(nameof(Float32TestCases))] + public async Task DoubleToDouble(double testCase) + { + var from = new DoubleValueMock { value = testCase }; + + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value, Is.EqualTo(testCase)); + } + + [Test] + [TestCase(123, 255)] + [TestCase(256, 1)] + [TestCase(256, float.MinValue)] + public async Task ListToMatrix64(int seed, double scalar) + { + Random rand = new(seed); + List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scalar).ToList(); + + ListDoubleValueMock from = new() { value = testCase, }; + + //Test List -> Matrix + var res = await from.SerializeAsTAndDeserialize(_operations); + Assert.That(res.value.M11, Is.EqualTo(testCase[0])); + Assert.That(res.value.M44, Is.EqualTo(testCase[testCase.Count - 1])); + + //Test Matrix -> List + var backAgain = await res.SerializeAsTAndDeserialize(_operations); + Assert.That(backAgain.value, Is.Not.Null); + Assert.That(backAgain.value, Is.EquivalentTo(testCase)); + } + + [Test] + [TestCase(123, 255)] + [TestCase(256, 1)] + [DefaultFloatingPointTolerance(Constants.EPS)] + public void Matrix32ToMatrix64(int seed, float scalar) + { + Random rand = new(seed); + List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scalar).ToList(); + + ListDoubleValueMock from = new() { value = testCase }; + + //Test List -> Matrix + var exception = Assert.ThrowsAsync( + async () => await from.SerializeAsTAndDeserialize(_operations) + ); + exception.ShouldNotBeNull(); + } +} + +public abstract class SerializerMock2 : Base +{ + private string _speckle_type; + + protected SerializerMock2() + { + _speckle_type = base.speckle_type; + } + + public override string speckle_type => _speckle_type; + + public void SerializeAs() + where T : Base, new() + { + T target = new(); + _speckle_type = target.speckle_type; + } + + internal async Task SerializeAsTAndDeserialize(IOperations operations) + where TTo : Base, new() + { + SerializeAs(); + + var json = operations.Serialize2(this); + + Base result = await operations.DeserializeAsync(json); + Assert.That(result, Is.Not.Null); + Assert.That(result, Is.TypeOf()); + return (TTo)result; + } +} + +public abstract class PrimitiveTestFixture2 +{ + public static readonly sbyte[] Int8TestCases = { default, sbyte.MaxValue, sbyte.MinValue }; + public static readonly short[] Int16TestCases = { short.MaxValue, short.MinValue }; + public static readonly int[] Int32TestCases = { int.MinValue, int.MaxValue }; + public static readonly long[] Int64TestCases = { long.MaxValue, long.MinValue }; + public static readonly ulong[] UInt64TestCases = { ulong.MaxValue, ulong.MinValue }; + + public static double[] Float64TestCases { get; } = + { + default, + double.Epsilon, + double.MaxValue, + double.MinValue, + double.PositiveInfinity, + double.NegativeInfinity, + double.NaN + }; + + public static float[] Float32TestCases { get; } = + { + default, + float.Epsilon, + float.MaxValue, + float.MinValue, + float.PositiveInfinity, + float.NegativeInfinity, + float.NaN + }; + + public static Half[] Float16TestCases { get; } = + { default, Half.Epsilon, Half.MaxValue, Half.MinValue, Half.PositiveInfinity, Half.NegativeInfinity, Half.NaN }; + + public static float[] FloatIntegralTestCases { get; } = { 0, 1, int.MaxValue, int.MinValue }; + + public static MyEnum[] MyEnums { get; } = Enum.GetValues(typeof(MyEnum)).Cast().ToArray(); +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests2.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests2.cs new file mode 100644 index 00000000..3c537bd4 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests2.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Speckle.Sdk.Api; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Tests.Unit.Host; + +namespace Speckle.Sdk.Tests.Unit.Serialisation; + +public class SimpleRoundTripTests2 +{ + private IOperations _operations; + + static SimpleRoundTripTests2() + { + Reset(); + } + + private static void Reset() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); + } + + public static IEnumerable TestData() + { + yield return new DiningTable { ["@strangeVariable_NAme3"] = new TableLegFixture() }; + + var polyline = new Polyline(); + for (int i = 0; i < 100; i++) + { + polyline.Points.Add(new Point { X = i * 2, Y = i % 2 }); + } + yield return polyline; + } + + [SetUp] + public void Setup() + { + Reset(); + + var serviceProvider = TestServiceSetup.GetServiceProvider(); + _operations = serviceProvider.GetRequiredService(); + } + + [TestCaseSource(nameof(TestData))] + public async Task SimpleSerialization(Base testData) + { + var result = _operations.Serialize2(testData); + var test = await _operations.DeserializeAsync(result); + + Assert.That(testData.GetId(), Is.EqualTo(test.GetId())); + } +} From 95caf9671fb20e9568b5e5209fd989c49b00368d Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 10:46:21 +0100 Subject: [PATCH 02/11] add pooling to serialization --- Directory.Packages.props | 9 +- src/Speckle.Objects/packages.lock.json | 27 +- .../Helpers/SerializerIdWriter2.cs | 626 ++++++++++++++++++ .../Helpers/SpeckleObjectSerializer2Pool.cs | 41 ++ .../Serialisation/SpeckleObjectSerializer2.cs | 17 +- src/Speckle.Sdk/Speckle.Sdk.csproj | 1 + src/Speckle.Sdk/packages.lock.json | 27 +- .../packages.lock.json | 6 + .../packages.lock.json | 6 + .../packages.lock.json | 6 + .../Benchmarks/GeneralSerializerTest.cs | 9 + .../packages.lock.json | 6 + .../Speckle.Sdk.Tests.Unit/packages.lock.json | 6 + 13 files changed, 766 insertions(+), 21 deletions(-) create mode 100644 src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs create mode 100644 src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 4ae02af2..cade34c9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,10 +8,11 @@ + - - - + + + @@ -30,4 +31,4 @@ - + \ No newline at end of file diff --git a/src/Speckle.Objects/packages.lock.json b/src/Speckle.Objects/packages.lock.json index bf429349..dbee06d0 100644 --- a/src/Speckle.Objects/packages.lock.json +++ b/src/Speckle.Objects/packages.lock.json @@ -121,6 +121,14 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", @@ -168,8 +176,8 @@ }, "System.Buffers": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.ComponentModel.Annotations": { "type": "Transitive", @@ -178,12 +186,12 @@ }, "System.Memory": { "type": "Transitive", - "resolved": "4.5.3", - "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", "dependencies": { - "System.Buffers": "4.4.0", + "System.Buffers": "4.5.1", "System.Numerics.Vectors": "4.4.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.2" + "System.Runtime.CompilerServices.Unsafe": "4.5.3" } }, "System.Numerics.Vectors": { @@ -238,6 +246,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", @@ -435,6 +444,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -498,6 +512,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", diff --git a/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs b/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs new file mode 100644 index 00000000..047b90f4 --- /dev/null +++ b/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs @@ -0,0 +1,626 @@ +using System.Text; +using Speckle.Newtonsoft.Json; + +namespace Speckle.Sdk.Helpers; + +public sealed class SerializerIdWriter2 : JsonWriter +{ + private readonly JsonWriter _jsonWriter; +#pragma warning disable CA2213 + private readonly JsonTextWriter _jsonIdWriter; +#pragma warning restore CA2213 + private readonly StreamWriter _idWriter; + private readonly MemoryStream _memoryStream; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _idWriter.Dispose(); + _memoryStream.Dispose(); + } + + public SerializerIdWriter2(JsonWriter jsonWriter, SpeckleObjectSerializer2Pool pool) + { + _jsonWriter = jsonWriter; + _memoryStream = pool.GetMemoryStream(); + _idWriter = new StreamWriter(_memoryStream); + _jsonIdWriter = pool.GetJsonTextWriter(_idWriter); + } + + public string FinishIdWriter() + { + _jsonIdWriter.WriteEndObject(); + _jsonIdWriter.Flush(); + var s = Encoding.UTF8.GetString(_memoryStream.GetBuffer(), 0, (int)_memoryStream.Length); + ((IDisposable)_jsonIdWriter).Dispose(); + return s; + } + + public override void WriteValue(string? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteStartArray() + { + _jsonWriter.WriteStartArray(); + _jsonIdWriter.WriteStartArray(); + } + + public override void WriteEndArray() + { + _jsonWriter.WriteEndArray(); + _jsonIdWriter.WriteEndArray(); + } + + public override void WriteStartObject() + { + _jsonWriter.WriteStartObject(); + _jsonIdWriter.WriteStartObject(); + } + + public override void WriteEndObject() + { + _jsonWriter.WriteEndObject(); + _jsonIdWriter.WriteEndObject(); + } + + public override void WriteComment(string? text) + { + _jsonWriter.WriteComment(text); + _jsonIdWriter.WriteComment(text); + } + + public override void WritePropertyName(string name) + { + _jsonWriter.WritePropertyName(name); + _jsonIdWriter.WritePropertyName(name); + } + + public override void WriteNull() + { + _jsonWriter.WriteNull(); + _jsonIdWriter.WriteNull(); + } + + public override void WriteUndefined() + { + _jsonWriter.WriteUndefined(); + _jsonIdWriter.WriteUndefined(); + } + + public override void WriteRaw(string? json) + { + _jsonWriter.WriteRaw(json); + _jsonIdWriter.WriteRaw(json); + } + + public override void WriteRawValue(string? json) + { + _jsonWriter.WriteRawValue(json); + _jsonIdWriter.WriteRawValue(json); + } + + public override void WriteValue(bool value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(bool? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(byte value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(byte? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(char value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(char? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(DateTime value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(DateTime? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(DateTimeOffset value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(DateTimeOffset? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(decimal value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(decimal? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(double value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(double? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(float value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(float? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(Guid value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(Guid? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(int value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(int? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(long value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(long? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(sbyte value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(sbyte? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(short value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(short? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(TimeSpan value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(TimeSpan? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(uint value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(uint? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(ulong value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(ulong? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(Uri? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(ushort value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(ushort? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(byte[]? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void WriteValue(object? value) + { + _jsonWriter.WriteValue(value); + _jsonIdWriter.WriteValue(value); + } + + public override void Flush() + { + _jsonWriter.Flush(); + _jsonIdWriter.Flush(); + } + + public override async Task WriteValueAsync(string? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteStartArrayAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteStartArrayAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteStartArrayAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteEndArrayAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteEndArrayAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteEndArrayAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteStartObjectAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteStartObjectAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteStartObjectAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteEndObjectAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteEndObjectAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteEndObjectAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteCommentAsync(string? text, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteCommentAsync(text, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteCommentAsync(text, cancellationToken).ConfigureAwait(false); + } + + public override async Task WritePropertyNameAsync(string name, CancellationToken cancellationToken = default) + { + await _jsonWriter.WritePropertyNameAsync(name, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WritePropertyNameAsync(name, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteNullAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteNullAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteNullAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteUndefinedAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteUndefinedAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteUndefinedAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteRawAsync(string? json, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteRawAsync(json, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteRawAsync(json, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteRawValueAsync(string? json, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteRawValueAsync(json, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteRawValueAsync(json, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(bool value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(bool? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(byte value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(byte? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(char value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(char? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(DateTime value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(DateTime? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(DateTimeOffset value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(DateTimeOffset? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(decimal value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(decimal? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(double value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(double? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(float value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(float? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(Guid value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(Guid? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(int value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(int? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(long value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(long? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(sbyte value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(sbyte? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(short value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(short? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(TimeSpan value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(TimeSpan? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(uint value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(uint? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(ulong value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(ulong? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(Uri? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(ushort value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(ushort? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(byte[]? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task WriteValueAsync(object? value, CancellationToken cancellationToken = default) + { + await _jsonWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.WriteValueAsync(value, cancellationToken).ConfigureAwait(false); + } + + public override async Task FlushAsync(CancellationToken cancellationToken = default) + { + await _jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + await _jsonIdWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs new file mode 100644 index 00000000..449e70ff --- /dev/null +++ b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs @@ -0,0 +1,41 @@ +using System.Buffers; +using Microsoft.IO; +using Speckle.Newtonsoft.Json; +using Speckle.Sdk.Common; + +namespace Speckle.Sdk.Helpers; + +public class SpeckleObjectSerializer2Pool +{ + public static readonly SpeckleObjectSerializer2Pool Instance = new(); + + private SpeckleObjectSerializer2Pool() { } + + public RecyclableMemoryStream GetMemoryStream() => _recyclableMemoryStreamManager.GetStream(); + + public JsonTextWriter GetJsonTextWriter(Stream stream) => new(new StreamWriter(stream)) { ArrayPool = _charPool }; + + public JsonTextWriter GetJsonTextWriter(TextWriter writer) => new(writer) { ArrayPool = _charPool }; + + public JsonTextReader GetJsonTextReader(TextReader reader) => new(reader) { ArrayPool = _charPool }; + + private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager = + new( + new RecyclableMemoryStreamManager.Options() + { + LargeBufferMultiple = 1024 * 1024, + MaximumBufferSize = 16 * 1024 * 1024, + MaximumLargePoolFreeBytes = 16 * 1024 * 1024 * 4, + MaximumSmallPoolFreeBytes = 40 * 1024 * 1024 + } + ); + + private readonly SerializerPool _charPool = new(ArrayPool.Create(4096, 4096)); + + private class SerializerPool(ArrayPool pool) : IArrayPool + { + public T[] Rent(int minimumLength) => pool.Rent(minimumLength); + + public void Return(T[]? array) => pool.Return(array.NotNull()); + } +} diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs index 014b7b1e..c4528e79 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs @@ -33,6 +33,8 @@ public class SpeckleObjectSerializer2 /// The sync transport. This transport will be used synchronously. public IReadOnlyCollection WriteTransports { get; } + public SpeckleObjectSerializer2Pool Pool { get; } = SpeckleObjectSerializer2Pool.Instance; + public CancellationToken CancellationToken { get; set; } public SpeckleObjectSerializer2() @@ -244,7 +246,7 @@ private void SerializeProperty( } using var writer = new StringWriter(); - using var jsonWriter = new JsonTextWriter(writer); + using var jsonWriter = Pool.GetJsonTextWriter(writer); string id = SerializeBaseObject(baseObj, jsonWriter, closure); var json = writer.ToString(); @@ -337,10 +339,13 @@ private void SerializeProperty( private string SerializeBaseObject(Base baseObj, JsonWriter writer, IReadOnlyDictionary closure) { var allProperties = ExtractAllProperties(baseObj); - + + SerializerIdWriter2? serializerIdWriter = null; + JsonWriter baseWriter = writer; if (baseObj is not Blob) { - writer = new SerializerIdWriter(writer); + serializerIdWriter = new SerializerIdWriter2(writer, Pool); + writer = serializerIdWriter; } writer.WriteStartObject(); @@ -357,9 +362,11 @@ private string SerializeBaseObject(Base baseObj, JsonWriter writer, IReadOnlyDic } string id; - if (writer is SerializerIdWriter serializerIdWriter) + if (serializerIdWriter is not null) { - (var json, writer) = serializerIdWriter.FinishIdWriter(); + var json = serializerIdWriter.FinishIdWriter(); + ((IDisposable)serializerIdWriter).Dispose(); + writer = baseWriter; id = ComputeId(json); } else diff --git a/src/Speckle.Sdk/Speckle.Sdk.csproj b/src/Speckle.Sdk/Speckle.Sdk.csproj index 3575ac76..fcd44aaa 100644 --- a/src/Speckle.Sdk/Speckle.Sdk.csproj +++ b/src/Speckle.Sdk/Speckle.Sdk.csproj @@ -29,6 +29,7 @@ + diff --git a/src/Speckle.Sdk/packages.lock.json b/src/Speckle.Sdk/packages.lock.json index d7266b92..e2fe848d 100644 --- a/src/Speckle.Sdk/packages.lock.json +++ b/src/Speckle.Sdk/packages.lock.json @@ -53,6 +53,15 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Direct", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -246,8 +255,8 @@ }, "System.Buffers": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.ComponentModel.Annotations": { "type": "Transitive", @@ -256,12 +265,12 @@ }, "System.Memory": { "type": "Transitive", - "resolved": "4.5.3", - "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", "dependencies": { - "System.Buffers": "4.4.0", + "System.Buffers": "4.5.1", "System.Numerics.Vectors": "4.4.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.2" + "System.Runtime.CompilerServices.Unsafe": "4.5.3" } }, "System.Numerics.Vectors": { @@ -361,6 +370,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Direct", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", diff --git a/tests/Speckle.Objects.Tests.Unit/packages.lock.json b/tests/Speckle.Objects.Tests.Unit/packages.lock.json index e1d02f86..3b08d861 100644 --- a/tests/Speckle.Objects.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Objects.Tests.Unit/packages.lock.json @@ -169,6 +169,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -278,6 +283,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", diff --git a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json index e1d02f86..3b08d861 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json @@ -169,6 +169,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -278,6 +283,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", diff --git a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json index 089dd8e4..b803e092 100644 --- a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json @@ -169,6 +169,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -272,6 +277,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index 97a6e6f4..b65b32f9 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -49,6 +49,15 @@ public string RunTest() var x = sut.Serialize(_testData); return x; } + + [Benchmark] + public string RunTest2() + { + var remote = new NullTransport(); + SpeckleObjectSerializer2 sut = new([remote]); + var x = sut.Serialize(_testData); + return x; + } } public class NullTransport : ITransport diff --git a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json index e9528b66..e553c5ba 100644 --- a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json @@ -224,6 +224,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -365,6 +370,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", diff --git a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json index e1755b70..9c75667d 100644 --- a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json @@ -184,6 +184,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -287,6 +292,7 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", From cccedd4048e68dfb3c7e17bdf2eb8347eff08d1d Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 10:59:59 +0100 Subject: [PATCH 03/11] add serialization tests --- src/Speckle.Objects/packages.lock.json | 28 ++++++++++--------- .../packages.lock.json | 11 ++++---- .../packages.lock.json | 11 ++++---- .../packages.lock.json | 11 ++++---- .../Benchmarks/GeneralSerializerTest.cs | 23 +++++++++++++-- .../packages.lock.json | 11 ++++---- .../Speckle.Sdk.Tests.Unit/packages.lock.json | 11 ++++---- 7 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/Speckle.Objects/packages.lock.json b/src/Speckle.Objects/packages.lock.json index dbee06d0..a27ad430 100644 --- a/src/Speckle.Objects/packages.lock.json +++ b/src/Speckle.Objects/packages.lock.json @@ -121,14 +121,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==", - "dependencies": { - "System.Memory": "4.5.5" - } - }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", @@ -299,6 +291,15 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", @@ -444,11 +445,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" - }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -565,6 +561,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", diff --git a/tests/Speckle.Objects.Tests.Unit/packages.lock.json b/tests/Speckle.Objects.Tests.Unit/packages.lock.json index 3b08d861..7b07061d 100644 --- a/tests/Speckle.Objects.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Objects.Tests.Unit/packages.lock.json @@ -169,11 +169,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" - }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -336,6 +331,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", diff --git a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json index 3b08d861..7b07061d 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json @@ -169,11 +169,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" - }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -336,6 +331,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", diff --git a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json index b803e092..5d3dbf4e 100644 --- a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json @@ -169,11 +169,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" - }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -352,6 +347,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index b65b32f9..68cff712 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -1,5 +1,8 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Order; using Microsoft.Extensions.Logging.Abstractions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; @@ -14,22 +17,38 @@ namespace Speckle.Sdk.Tests.Performance.Benchmarks; /// /// How many threads on our Deserializer is optimal /// +[Config(typeof(Config))] +[RankColumn] [MemoryDiagnoser] -[SimpleJob(RunStrategy.Monitoring)] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] public class GeneralSerializerTest { private Base _testData; + + private class Config : ManualConfig + { + public Config() + { + var job = Job.ShortRun.WithLaunchCount(0).WithWarmupCount(0).WithIterationCount(1); + AddJob(job); + } + } [GlobalSetup] public async Task Setup() { TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); + + var url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small? + +//var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? + using var dataSource = new TestDataHelper(); await dataSource .SeedTransport( new Account() { - serverInfo = new() { url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e" } + serverInfo = new() { url = url } }, "2099ac4b5f", "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6" diff --git a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json index e553c5ba..7b6fa582 100644 --- a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json @@ -224,11 +224,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" - }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -423,6 +418,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", diff --git a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json index 9c75667d..b19f5925 100644 --- a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json @@ -184,11 +184,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Transitive", - "resolved": "3.0.1", - "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" - }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -345,6 +340,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, "Polly": { "type": "CentralTransitive", "requested": "[7.2.3, )", From f714926da39b379cfdf85506fd17499afb67846d Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 11:32:42 +0100 Subject: [PATCH 04/11] make deserialization not be async and use pool more --- Speckle.Sdk.sln | 7 + .../Api/Operations/Operations.Receive.cs | 6 +- .../SpeckleObjectDeserializer.cs | 49 +- .../Serialisation/SpeckleObjectSerializer2.cs | 28 +- .../Serialisation/Utilities/ClosureParser.cs | 60 +-- src/Speckle.Sdk/Transports/ServerTransport.cs | 6 +- .../Transports/TransportHelpers.cs | 2 +- .../NullTransport.cs | 33 ++ .../Program.cs | 38 ++ .../Speckle.Sdk.Performance.Testing.csproj | 15 + .../packages.lock.json | 470 ++++++++++++++++++ 11 files changed, 648 insertions(+), 66 deletions(-) create mode 100644 tests/Speckle.Sdk.Performance.Testing/NullTransport.cs create mode 100644 tests/Speckle.Sdk.Performance.Testing/Program.cs create mode 100644 tests/Speckle.Sdk.Performance.Testing/Speckle.Sdk.Performance.Testing.csproj create mode 100644 tests/Speckle.Sdk.Performance.Testing/packages.lock.json diff --git a/Speckle.Sdk.sln b/Speckle.Sdk.sln index bb0e5bbd..5883f32e 100644 --- a/Speckle.Sdk.sln +++ b/Speckle.Sdk.sln @@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Tests.Performance", "tests\Speckle.Sdk.Tests.Performance\Speckle.Sdk.Tests.Performance.csproj", "{870E3396-E6F7-43AE-B120-E651FA4F46BD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Performance.Testing", "tests\Speckle.Sdk.Performance.Testing\Speckle.Sdk.Performance.Testing.csproj", "{F3423F2A-3C83-4F4A-94CF-5D2214BF6CC9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +86,10 @@ Global {870E3396-E6F7-43AE-B120-E651FA4F46BD}.Debug|Any CPU.Build.0 = Debug|Any CPU {870E3396-E6F7-43AE-B120-E651FA4F46BD}.Release|Any CPU.ActiveCfg = Release|Any CPU {870E3396-E6F7-43AE-B120-E651FA4F46BD}.Release|Any CPU.Build.0 = Release|Any CPU + {F3423F2A-3C83-4F4A-94CF-5D2214BF6CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3423F2A-3C83-4F4A-94CF-5D2214BF6CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3423F2A-3C83-4F4A-94CF-5D2214BF6CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3423F2A-3C83-4F4A-94CF-5D2214BF6CC9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {A413E196-3696-4F48-B635-04B5F76BF9C9} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116} @@ -95,5 +101,6 @@ Global {4FB41A6D-D139-4111-8115-E3F9F6BEAF24} = {35047EE7-AD1D-4741-80A7-8F0E874718E9} {B623BD21-5CAA-43F9-A539-1835276C220E} = {DA2AED52-58F9-471E-8AD8-102FD36129E3} {870E3396-E6F7-43AE-B120-E651FA4F46BD} = {35047EE7-AD1D-4741-80A7-8F0E874718E9} + {F3423F2A-3C83-4F4A-94CF-5D2214BF6CC9} = {35047EE7-AD1D-4741-80A7-8F0E874718E9} EndGlobalSection EndGlobal diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs index 9c3e7536..3c765f61 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs @@ -140,9 +140,9 @@ public async Task Receive( } // Shoot out the total children count, wasteful - var count = ( - await ClosureParser.GetClosuresAsync(objString, localTransport.CancellationToken).ConfigureAwait(false) - ).Count; + var count = + ClosureParser.GetClosures(objString) + .Count; onTotalChildrenCountKnown?.Invoke(count); diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs index 84b99404..d488d584 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs @@ -45,7 +45,7 @@ public sealed class SpeckleObjectDeserializer /// was null /// cannot be deserialised to type // /// did not contain the required json objects (closures) - public async Task DeserializeAsync(string rootObjectJson) + public async ValueTask DeserializeAsync(string rootObjectJson) { if (_isBusy) { @@ -68,7 +68,7 @@ public async Task DeserializeAsync(string rootObjectJson) } } - private async Task DeserializeJsonAsyncInternal(string objectJson) + private async ValueTask DeserializeJsonAsyncInternal(string objectJson) { if (objectJson is null) { @@ -85,7 +85,7 @@ public async Task DeserializeAsync(string rootObjectJson) object? converted; try { - await reader.ReadAsync(CancellationToken).ConfigureAwait(false); + reader.Read(); converted = await ReadObjectAsync(reader, CancellationToken).ConfigureAwait(false); } catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) @@ -105,9 +105,9 @@ public async Task DeserializeAsync(string rootObjectJson) } //this should be buffered - private async Task> ReadArrayAsync(JsonReader reader, CancellationToken ct) + private async ValueTask> ReadArrayAsync(JsonReader reader, CancellationToken ct) { - await reader.ReadAsync(ct).ConfigureAwait(false); + reader.Read(); List retList = new(); while (reader.TokenType != JsonToken.EndArray) { @@ -120,14 +120,14 @@ public async Task DeserializeAsync(string rootObjectJson) { retList.Add(convertedValue); } - await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next + reader.Read(); //goes to next } return retList; } - private async Task ReadObjectAsync(JsonReader reader, CancellationToken ct) + private async ValueTask ReadObjectAsync(JsonReader reader, CancellationToken ct) { - await reader.ReadAsync(ct).ConfigureAwait(false); + reader.Read(); Dictionary dict = new(); while (reader.TokenType != JsonToken.EndObject) { @@ -138,8 +138,8 @@ public async Task DeserializeAsync(string rootObjectJson) string propName = (reader.Value?.ToString()).NotNull(); if (propName == "__closure") { - await reader.ReadAsync(ct).ConfigureAwait(false); //goes to prop value - var closures = await ClosureParser.GetClosuresAsync(reader).ConfigureAwait(false); + reader.Read(); //goes to prop value + var closures = ClosureParser.GetClosures(reader); foreach (var closure in closures) { _ids.Add(closure.Item1); @@ -152,13 +152,13 @@ public async Task DeserializeAsync(string rootObjectJson) // https://linear.app/speckle/issue/CXPLA-54/when-deserializing-dont-allow-closures-that-arent-downloadable await TryGetDeserializedAsync(objId).ConfigureAwait(false); } - await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next + reader.Read(); //goes to next continue; } - await reader.ReadAsync(ct).ConfigureAwait(false); //goes prop value + reader.Read(); //goes prop value object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); dict[propName] = convertedValue; - await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next + reader.Read(); //goes to next } break; default: @@ -181,7 +181,7 @@ public async Task DeserializeAsync(string rootObjectJson) return Dict2Base(dict); } - private async Task TryGetDeserializedAsync(string objId) + private async ValueTask TryGetDeserializedAsync(string objId) { object? deserialized = null; _deserializedObjects.NotNull(); @@ -190,6 +190,22 @@ public async Task DeserializeAsync(string rootObjectJson) deserialized = o; } + if (deserialized is ValueTask valueTask) + { + try + { + deserialized = valueTask.Result; + } + catch (AggregateException ex) + { + throw new SpeckleDeserializeException("Failed to deserialize reference object", ex); + } + + if (_deserializedObjects.TryAdd(objId, deserialized)) + { + _currentCount++; + } + } if (deserialized is Task task) { try @@ -228,9 +244,8 @@ public async Task DeserializeAsync(string rootObjectJson) return deserialized; } - private async Task ReadPropertyAsync(JsonReader reader, CancellationToken ct) + private async ValueTask ReadPropertyAsync(JsonReader reader, CancellationToken ct) { - ct.ThrowIfCancellationRequested(); switch (reader.TokenType) { case JsonToken.Undefined: @@ -258,7 +273,7 @@ public async Task DeserializeAsync(string rootObjectJson) case JsonToken.Float: return (double)reader.Value.NotNull(); case JsonToken.String: - return (string?)reader.Value.NotNull(); + return (string)reader.Value.NotNull(); case JsonToken.Date: return (DateTime)reader.Value.NotNull(); case JsonToken.StartArray: diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs index c4528e79..bb481720 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs @@ -245,10 +245,15 @@ private void SerializeProperty( _parentClosures.Add(closure); } - using var writer = new StringWriter(); - using var jsonWriter = Pool.GetJsonTextWriter(writer); - string id = SerializeBaseObject(baseObj, jsonWriter, closure); - var json = writer.ToString(); + string id; + string json; + using (var memoryStream = Pool.GetMemoryStream()) + using (var writer = new StreamWriter(memoryStream)) + { + using var jsonWriter = Pool.GetJsonTextWriter(writer); + id = SerializeBaseObject(baseObj, jsonWriter, closure); + json = writer.ToString().NotNull(); + } if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) { @@ -269,11 +274,16 @@ private void SerializeProperty( StoreObject(id, json); ObjectReference objRef = new() { referencedId = id }; - using var writer2 = new StringWriter(); - using var jsonWriter2 = new JsonTextWriter(writer2); - SerializeProperty(objRef, jsonWriter2); - var json2 = writer2.ToString(); - UpdateParentClosures(id); + + string json2; + using (var memoryStream = Pool.GetMemoryStream()) + using (var writer2 = new StreamWriter(memoryStream)) + { + using var jsonWriter2 = Pool.GetJsonTextWriter(writer2); + SerializeProperty(objRef, jsonWriter2); + json2 = writer2.ToString().NotNull(); + UpdateParentClosures(id); + } _onProgressAction?.Invoke(new(ProgressEvent.SerializeObject, ++_serializedCount, null)); diff --git a/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs b/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs index 70598054..a6bbee60 100644 --- a/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs +++ b/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs @@ -1,31 +1,30 @@ using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; +using Speckle.Sdk.Helpers; namespace Speckle.Sdk.Serialisation.Utilities; - public static class ClosureParser { - public static async Task> GetClosuresAsync( - string rootObjectJson, - CancellationToken cancellationToken = default - ) + public static IReadOnlyList<(string, int)> GetClosures(string rootObjectJson) { try { - using JsonTextReader reader = new(new StringReader(rootObjectJson)); - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); + using JsonTextReader reader = SpeckleObjectSerializer2Pool.Instance.GetJsonTextReader( + new StringReader(rootObjectJson) + ); + reader.Read(); while (reader.TokenType != JsonToken.EndObject) { switch (reader.TokenType) { case JsonToken.StartObject: { - var closureList = await ReadObjectAsync(reader, cancellationToken).ConfigureAwait(false); + var closureList = ReadObject(reader); return closureList; } default: - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); - await reader.SkipAsync(cancellationToken).ConfigureAwait(false); + reader.Read(); + reader.Skip(); break; } } @@ -34,17 +33,12 @@ public static class ClosureParser return []; } - public static async Task> GetChildrenIdsAsync( - string rootObjectJson, - CancellationToken cancellationToken = default - ) => (await GetClosuresAsync(rootObjectJson, cancellationToken).ConfigureAwait(false)).Select(x => x.Item1); + public static IEnumerable GetChildrenIds(string rootObjectJson) => + GetClosures(rootObjectJson).Select(x => x.Item1); - private static async Task> ReadObjectAsync( - JsonTextReader reader, - CancellationToken cancellationToken - ) + private static IReadOnlyList<(string, int)> ReadObject(JsonTextReader reader) { - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); + reader.Read(); while (reader.TokenType != JsonToken.EndObject) { switch (reader.TokenType) @@ -53,46 +47,46 @@ CancellationToken cancellationToken { if (reader.Value as string == "__closure") { - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); //goes to prop vale - var closureList = await ReadClosureEnumerableAsync(reader).ConfigureAwait(false); + reader.Read(); //goes to prop vale + var closureList = ReadClosureEnumerable(reader); return closureList; } - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); //goes to prop vale - await reader.SkipAsync(cancellationToken).ConfigureAwait(false); - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); //goes to next + reader.Read(); //goes to prop vale + reader.Skip(); + reader.Read(); //goes to next } break; default: - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); - await reader.SkipAsync(cancellationToken).ConfigureAwait(false); - await reader.ReadAsync(cancellationToken).ConfigureAwait(false); + reader.Read(); + reader.Skip(); + reader.Read(); break; } } return []; } - public static async Task> GetClosuresAsync(JsonReader reader) + public static IReadOnlyList<(string, int)> GetClosures(JsonReader reader) { if (reader.TokenType != JsonToken.StartObject) { return Array.Empty<(string, int)>(); } - var closureList = await ReadClosureEnumerableAsync(reader).ConfigureAwait(false); + var closureList = ReadClosureEnumerable(reader); closureList.Sort((a, b) => b.Item2.CompareTo(a.Item2)); return closureList; } - private static async Task> ReadClosureEnumerableAsync(JsonReader reader) + private static List<(string, int)> ReadClosureEnumerable(JsonReader reader) { List<(string, int)> closureList = new(); - await reader.ReadAsync().ConfigureAwait(false); //startobject + reader.Read(); //startobject while (reader.TokenType != JsonToken.EndObject) { var childId = (reader.Value as string).NotNull(); // propertyName - int childMinDepth = (await reader.ReadAsInt32Async().ConfigureAwait(false)).NotNull(); //propertyValue - await reader.ReadAsync().ConfigureAwait(false); + int childMinDepth = (reader.ReadAsInt32()).NotNull(); //propertyValue + reader.Read(); closureList.Add((childId, childMinDepth)); } return closureList; diff --git a/src/Speckle.Sdk/Transports/ServerTransport.cs b/src/Speckle.Sdk/Transports/ServerTransport.cs index 47045f1f..e53b437d 100644 --- a/src/Speckle.Sdk/Transports/ServerTransport.cs +++ b/src/Speckle.Sdk/Transports/ServerTransport.cs @@ -140,9 +140,9 @@ public async Task CopyObjectAndChildren( api.CancellationToken = CancellationToken; string? rootObjectJson = await api.DownloadSingleObject(StreamId, id, OnProgressAction).ConfigureAwait(false); - var allIds = ( - await ClosureParser.GetChildrenIdsAsync(rootObjectJson.NotNull(), CancellationToken).ConfigureAwait(false) - ).ToList(); + var allIds = + ClosureParser.GetChildrenIds(rootObjectJson.NotNull()) + .ToList(); var childrenIds = allIds.Where(x => !x.Contains("blob:")); var blobIds = allIds.Where(x => x.Contains("blob:")).Select(x => x.Remove(0, 5)); diff --git a/src/Speckle.Sdk/Transports/TransportHelpers.cs b/src/Speckle.Sdk/Transports/TransportHelpers.cs index 7111d193..466aeb7d 100644 --- a/src/Speckle.Sdk/Transports/TransportHelpers.cs +++ b/src/Speckle.Sdk/Transports/TransportHelpers.cs @@ -30,7 +30,7 @@ CancellationToken cancellationToken targetTransport.SaveObject(id, parent); - var closures = (await ClosureParser.GetChildrenIdsAsync(parent, cancellationToken).ConfigureAwait(false)).ToList(); + var closures = ClosureParser.GetChildrenIds(parent).ToList(); onTotalChildrenCountKnown?.Invoke(closures.Count); diff --git a/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs b/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs new file mode 100644 index 00000000..e8068b28 --- /dev/null +++ b/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs @@ -0,0 +1,33 @@ +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Performance.Testing; +public class NullTransport : ITransport +{ + public string TransportName { get; set; } = ""; + public Dictionary TransportContext { get; } = new(); + public TimeSpan Elapsed { get; } = TimeSpan.Zero; + public CancellationToken CancellationToken { get; set; } + public Action? OnProgressAction { get; set; } + + public void BeginWrite() { } + + public void EndWrite() { } + + public void SaveObject(string id, string serializedObject) { } + + public Task WriteComplete() + { + return Task.CompletedTask; + } + + public Task GetObject(string id) => throw new NotImplementedException(); + + public Task CopyObjectAndChildren( + string id, + ITransport targetTransport, + Action? onTotalChildrenCountKnown = null + ) => throw new NotImplementedException(); + + public Task> HasObjects(IReadOnlyList objectIds) => + throw new NotImplementedException(); +} diff --git a/tests/Speckle.Sdk.Performance.Testing/Program.cs b/tests/Speckle.Sdk.Performance.Testing/Program.cs new file mode 100644 index 00000000..c21c6f6f --- /dev/null +++ b/tests/Speckle.Sdk.Performance.Testing/Program.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using Speckle.Sdk.Common; +using Speckle.Sdk.Credentials; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation; +using Speckle.Sdk.Tests.Performance; + +TypeLoader.Reset(); +TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); + +var url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small? + +//var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? + +using var dataSource = new TestDataHelper(); +await dataSource + .SeedTransport( + new Account() + { + serverInfo = new() { url = url } + }, + "2099ac4b5f", + "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6" + ) + .ConfigureAwait(false); + +SpeckleObjectDeserializer deserializer = new() { ReadTransport = dataSource.Transport }; +string data = await dataSource.Transport.GetObject(dataSource.ObjectId).NotNull().ConfigureAwait(false); +var testData = await deserializer.DeserializeAsync(data).ConfigureAwait(false); + +Console.WriteLine("Attach"); +Console.ReadLine(); +Console.WriteLine("Executing"); +SpeckleObjectSerializer2 sut = new(); +var x = sut.Serialize(testData); +Console.WriteLine("Detach"); +Console.ReadLine(); diff --git a/tests/Speckle.Sdk.Performance.Testing/Speckle.Sdk.Performance.Testing.csproj b/tests/Speckle.Sdk.Performance.Testing/Speckle.Sdk.Performance.Testing.csproj new file mode 100644 index 00000000..000d4525 --- /dev/null +++ b/tests/Speckle.Sdk.Performance.Testing/Speckle.Sdk.Performance.Testing.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/tests/Speckle.Sdk.Performance.Testing/packages.lock.json b/tests/Speckle.Sdk.Performance.Testing/packages.lock.json new file mode 100644 index 00000000..ae920033 --- /dev/null +++ b/tests/Speckle.Sdk.Performance.Testing/packages.lock.json @@ -0,0 +1,470 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "GitVersion.MsBuild": { + "type": "Direct", + "requested": "[5.12.0, )", + "resolved": "5.12.0", + "contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.14.1, )", + "resolved": "1.14.1", + "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + }, + "Speckle.InterfaceGenerator": { + "type": "Direct", + "requested": "[0.9.6, )", + "resolved": "0.9.6", + "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" + }, + "BenchmarkDotNet.Annotations": { + "type": "Transitive", + "resolved": "0.14.0", + "contentHash": "CUDCg6bgHrDzhjnA+IOBl5gAo8Y5hZ2YSs7MBXrYMlMKpBZqrD5ez0537uDveOkcf+YWAoK+S4sMcuWPbIz8bw==" + }, + "CommandLineParser": { + "type": "Transitive", + "resolved": "2.9.1", + "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==" + }, + "Gee.External.Capstone": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" + }, + "GraphQL.Client.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==", + "dependencies": { + "GraphQL.Primitives": "6.0.0" + } + }, + "GraphQL.Client.Abstractions.Websocket": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==", + "dependencies": { + "GraphQL.Client.Abstractions": "6.0.0" + } + }, + "GraphQL.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA==" + }, + "Iced": { + "type": "Transitive", + "resolved": "1.17.0", + "contentHash": "8x+HCVTl/HHTGpscH3vMBhV8sknN/muZFw9s3TsI8SA6+c43cOTCi2+jE4KsU8pNLbJ++iF2ZFcpcXHXtDglnw==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.3.3", + "contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "bNzTyxP3iD5FPFHfVDl15Y6/wSoI7e3MeV0lOaj9igbIKTjgrmuw6LoVJ06jUNFA7+KaDC/OIsStWl/FQJz6sQ==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.3", + "System.Collections.Immutable": "5.0.0", + "System.Memory": "4.5.4", + "System.Reflection.Metadata": "5.0.0", + "System.Runtime.CompilerServices.Unsafe": "5.0.0", + "System.Text.Encoding.CodePages": "4.5.1", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "sbu6kDGzo9bfQxuqWpeEE7I9P30bSuZEnpDz9/qz20OU6pm79Z63+/BsAzO2e/R/Q97kBrpj647wokZnEVr97w==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.1.0]" + } + }, + "Microsoft.Data.Sqlite.Core": { + "type": "Transitive", + "resolved": "7.0.7", + "contentHash": "21FRzcJhaTrlv7kTrqr/ltFcSQM2TyuTTPhUcjO8H73od7Bb3QraNW90c7lUucNI/245XPkKZG4fp7/7OsKCSg==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.251802", + "contentHash": "bqnYl6AdSeboeN4v25hSukK6Odm6/54E3Y2B8rBvgqvAW0mF8fo7XNRVE2DMOG7Rk0fiuA079QIH28+V+W1Zdg==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "1.1.0", + "Microsoft.Extensions.Logging": "2.1.1" + } + }, + "Microsoft.Diagnostics.Runtime": { + "type": "Transitive", + "resolved": "2.2.332302", + "contentHash": "Hp84ivxSKIMTBzYSATxmUsm3YSXHWivcwiRRbsydGmqujMUK8BAueLN0ssAVEOkOBmh0vjUBhrq7YcroT7VCug==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.251802", + "System.Collections.Immutable": "5.0.0", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "Microsoft.Diagnostics.Tracing.TraceEvent": { + "type": "Transitive", + "resolved": "3.1.8", + "contentHash": "kl3UMrZKSeSEYZ8rt/GjLUQToREjgQABqfg6PzQBmSlYHTZOKE9ePEOS2xptROQ9SVvngg3QGX51TIT11iZ0wA==", + "dependencies": { + "Microsoft.Win32.Registry": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "2.2.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Primitives": "2.2.0", + "System.ComponentModel.Annotations": "4.5.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", + "dependencies": { + "System.Memory": "4.5.1", + "System.Runtime.CompilerServices.Unsafe": "4.5.1" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Perfolizer": { + "type": "Transitive", + "resolved": "0.3.17", + "contentHash": "FQgtCoF2HFwvzKWulAwBS5BGLlh8pgbrJtOp47jyBwh2CW16juVtacN1azOA2BqdrJXkXTNLNRMo7ZlHHiuAnA==" + }, + "SQLitePCLRaw.bundle_e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.4", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.4" + } + }, + "SQLitePCLRaw.core": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg==" + }, + "SQLitePCLRaw.provider.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "JPJArwA1kdj8qDAkY2XGjSWoYnqiM7q/3yRNkt6n28Mnn95MuEGkZXUbPBf7qc3IjwrGY5ttQon7yqHZyQJmOQ==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==" + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "MF1CHaRcC+MLFdnDthv4/bKWBZnlnSpkGqa87pKukQefgEdwtb9zFW6zs0GjPp73qtpYYg4q6PEKbzJbxCpKfw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "Microsoft.Win32.Registry": "5.0.0", + "System.CodeDom": "5.0.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.1.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, + "speckle.objects": { + "type": "Project", + "dependencies": { + "Speckle.Sdk": "[1.0.0, )" + } + }, + "speckle.sdk": { + "type": "Project", + "dependencies": { + "GraphQL.Client": "[6.0.0, )", + "Microsoft.CSharp": "[4.7.0, )", + "Microsoft.Data.Sqlite": "[7.0.7, )", + "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", + "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", + "Polly": "[7.2.3, )", + "Polly.Contrib.WaitAndRetry": "[1.1.1, )", + "Polly.Extensions.Http": "[3.0.0, )", + "Speckle.DoubleNumerics": "[4.0.1, )", + "Speckle.Newtonsoft.Json": "[13.0.2, )" + } + }, + "speckle.sdk.tests.performance": { + "type": "Project", + "dependencies": { + "BenchmarkDotNet": "[0.14.0, )", + "Microsoft.Extensions.DependencyInjection": "[2.2.0, )", + "Speckle.Objects": "[1.0.0, )" + } + }, + "BenchmarkDotNet": { + "type": "CentralTransitive", + "requested": "[0.14.0, )", + "resolved": "0.14.0", + "contentHash": "eIPSDKi3oni734M1rt/XJAwGQQOIf9gLjRRKKJ0HuVy3vYd7gnmAIX1bTjzI9ZbAY/nPddgqqgM/TeBYitMCIg==", + "dependencies": { + "BenchmarkDotNet.Annotations": "0.14.0", + "CommandLineParser": "2.9.1", + "Gee.External.Capstone": "2.3.0", + "Iced": "1.17.0", + "Microsoft.CodeAnalysis.CSharp": "4.1.0", + "Microsoft.Diagnostics.Runtime": "2.2.332302", + "Microsoft.Diagnostics.Tracing.TraceEvent": "3.1.8", + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Perfolizer": "[0.3.17]", + "System.Management": "5.0.0" + } + }, + "GraphQL.Client": { + "type": "CentralTransitive", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==", + "dependencies": { + "GraphQL.Client.Abstractions": "6.0.0", + "GraphQL.Client.Abstractions.Websocket": "6.0.0", + "System.Reactive": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Data.Sqlite": { + "type": "CentralTransitive", + "requested": "[7.0.7, )", + "resolved": "7.0.7", + "contentHash": "tiNmV1oPy+Z2R7Wd0bPB/FxCr8B+/5q11OpDMG751GA/YuOL7MZrBFfzv5oFRlFe08K6sjrnbrauzzGIeNrzLQ==", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "7.0.7", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.4" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[2.2.0, )", + "resolved": "2.2.0", + "contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "CentralTransitive", + "requested": "[2.2.0, )", + "resolved": "2.2.0", + "contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw==" + }, + "Microsoft.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[2.2.0, )", + "resolved": "2.2.0", + "contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "2.2.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, + "Polly": { + "type": "CentralTransitive", + "requested": "[7.2.3, )", + "resolved": "7.2.3", + "contentHash": "DeCY0OFbNdNxsjntr1gTXHJ5pKUwYzp04Er2LLeN3g6pWhffsGuKVfMBLe1lw7x76HrPkLxKEFxBlpRxS2nDEQ==" + }, + "Polly.Contrib.WaitAndRetry": { + "type": "CentralTransitive", + "requested": "[1.1.1, )", + "resolved": "1.1.1", + "contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA==" + }, + "Polly.Extensions.Http": { + "type": "CentralTransitive", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==", + "dependencies": { + "Polly": "7.1.0" + } + }, + "Speckle.DoubleNumerics": { + "type": "CentralTransitive", + "requested": "[4.0.1, )", + "resolved": "4.0.1", + "contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w==" + }, + "Speckle.Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.2, )", + "resolved": "13.0.2", + "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + } + } + } +} \ No newline at end of file From 7a33daec83263e4e4c93e46b5535b9b4f6072949 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 11:36:13 +0100 Subject: [PATCH 05/11] fmt --- .../Api/Operations/Operations.Receive.cs | 4 +--- .../Api/Operations/Operations.Serialize.cs | 2 ++ src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs | 2 +- .../Serialisation/SpeckleObjectDeserializer.cs | 18 +++++++++--------- .../Serialisation/SpeckleObjectSerializer2.cs | 8 ++++---- .../Serialisation/Utilities/ClosureParser.cs | 1 + src/Speckle.Sdk/Transports/ServerTransport.cs | 4 +--- src/Speckle.Sdk/Transports/TransportHelpers.cs | 2 +- .../NullTransport.cs | 1 + .../Speckle.Sdk.Performance.Testing/Program.cs | 9 +-------- .../Benchmarks/GeneralSerializerTest.cs | 15 ++++++--------- 11 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs index 3c765f61..0354844e 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs @@ -140,9 +140,7 @@ public async Task Receive( } // Shoot out the total children count, wasteful - var count = - ClosureParser.GetClosures(objString) - .Count; + var count = ClosureParser.GetClosures(objString).Count; onTotalChildrenCountKnown?.Invoke(count); diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs index ebc16a64..2efb8355 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs @@ -23,11 +23,13 @@ public string Serialize(Base value, CancellationToken cancellationToken = defaul var serializer = new SpeckleObjectSerializer { CancellationToken = cancellationToken }; return serializer.Serialize(value); } + public string Serialize2(Base value, CancellationToken cancellationToken = default) { var serializer = new SpeckleObjectSerializer2 { CancellationToken = cancellationToken }; return serializer.Serialize(value); } + /// /// Note: if you want to pull an object from a Speckle Transport or Server, /// please use diff --git a/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs b/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs index 047b90f4..cf2eba75 100644 --- a/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs +++ b/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs @@ -11,7 +11,7 @@ public sealed class SerializerIdWriter2 : JsonWriter #pragma warning restore CA2213 private readonly StreamWriter _idWriter; private readonly MemoryStream _memoryStream; - + protected override void Dispose(bool disposing) { base.Dispose(disposing); diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs index d488d584..83742853 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs @@ -85,7 +85,7 @@ public async ValueTask DeserializeAsync(string rootObjectJson) object? converted; try { - reader.Read(); + reader.Read(); converted = await ReadObjectAsync(reader, CancellationToken).ConfigureAwait(false); } catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) @@ -107,7 +107,7 @@ public async ValueTask DeserializeAsync(string rootObjectJson) //this should be buffered private async ValueTask> ReadArrayAsync(JsonReader reader, CancellationToken ct) { - reader.Read(); + reader.Read(); List retList = new(); while (reader.TokenType != JsonToken.EndArray) { @@ -120,14 +120,14 @@ public async ValueTask DeserializeAsync(string rootObjectJson) { retList.Add(convertedValue); } - reader.Read(); //goes to next + reader.Read(); //goes to next } return retList; } private async ValueTask ReadObjectAsync(JsonReader reader, CancellationToken ct) { - reader.Read(); + reader.Read(); Dictionary dict = new(); while (reader.TokenType != JsonToken.EndObject) { @@ -138,8 +138,8 @@ public async ValueTask DeserializeAsync(string rootObjectJson) string propName = (reader.Value?.ToString()).NotNull(); if (propName == "__closure") { - reader.Read(); //goes to prop value - var closures = ClosureParser.GetClosures(reader); + reader.Read(); //goes to prop value + var closures = ClosureParser.GetClosures(reader); foreach (var closure in closures) { _ids.Add(closure.Item1); @@ -152,13 +152,13 @@ public async ValueTask DeserializeAsync(string rootObjectJson) // https://linear.app/speckle/issue/CXPLA-54/when-deserializing-dont-allow-closures-that-arent-downloadable await TryGetDeserializedAsync(objId).ConfigureAwait(false); } - reader.Read(); //goes to next + reader.Read(); //goes to next continue; } - reader.Read(); //goes prop value + reader.Read(); //goes prop value object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); dict[propName] = convertedValue; - reader.Read(); //goes to next + reader.Read(); //goes to next } break; default: diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs index bb481720..9151af29 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs @@ -251,8 +251,8 @@ private void SerializeProperty( using (var writer = new StreamWriter(memoryStream)) { using var jsonWriter = Pool.GetJsonTextWriter(writer); - id = SerializeBaseObject(baseObj, jsonWriter, closure); - json = writer.ToString().NotNull(); + id = SerializeBaseObject(baseObj, jsonWriter, closure); + json = writer.ToString().NotNull(); } if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) @@ -274,7 +274,7 @@ private void SerializeProperty( StoreObject(id, json); ObjectReference objRef = new() { referencedId = id }; - + string json2; using (var memoryStream = Pool.GetMemoryStream()) using (var writer2 = new StreamWriter(memoryStream)) @@ -349,7 +349,7 @@ private void SerializeProperty( private string SerializeBaseObject(Base baseObj, JsonWriter writer, IReadOnlyDictionary closure) { var allProperties = ExtractAllProperties(baseObj); - + SerializerIdWriter2? serializerIdWriter = null; JsonWriter baseWriter = writer; if (baseObj is not Blob) diff --git a/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs b/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs index a6bbee60..7c2abd18 100644 --- a/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs +++ b/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs @@ -3,6 +3,7 @@ using Speckle.Sdk.Helpers; namespace Speckle.Sdk.Serialisation.Utilities; + public static class ClosureParser { public static IReadOnlyList<(string, int)> GetClosures(string rootObjectJson) diff --git a/src/Speckle.Sdk/Transports/ServerTransport.cs b/src/Speckle.Sdk/Transports/ServerTransport.cs index e53b437d..cdd997ca 100644 --- a/src/Speckle.Sdk/Transports/ServerTransport.cs +++ b/src/Speckle.Sdk/Transports/ServerTransport.cs @@ -140,9 +140,7 @@ public async Task CopyObjectAndChildren( api.CancellationToken = CancellationToken; string? rootObjectJson = await api.DownloadSingleObject(StreamId, id, OnProgressAction).ConfigureAwait(false); - var allIds = - ClosureParser.GetChildrenIds(rootObjectJson.NotNull()) - .ToList(); + var allIds = ClosureParser.GetChildrenIds(rootObjectJson.NotNull()).ToList(); var childrenIds = allIds.Where(x => !x.Contains("blob:")); var blobIds = allIds.Where(x => x.Contains("blob:")).Select(x => x.Remove(0, 5)); diff --git a/src/Speckle.Sdk/Transports/TransportHelpers.cs b/src/Speckle.Sdk/Transports/TransportHelpers.cs index 466aeb7d..87705d16 100644 --- a/src/Speckle.Sdk/Transports/TransportHelpers.cs +++ b/src/Speckle.Sdk/Transports/TransportHelpers.cs @@ -30,7 +30,7 @@ CancellationToken cancellationToken targetTransport.SaveObject(id, parent); - var closures = ClosureParser.GetChildrenIds(parent).ToList(); + var closures = ClosureParser.GetChildrenIds(parent).ToList(); onTotalChildrenCountKnown?.Invoke(closures.Count); diff --git a/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs b/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs index e8068b28..3c348c92 100644 --- a/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs +++ b/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs @@ -1,6 +1,7 @@ using Speckle.Sdk.Transports; namespace Speckle.Sdk.Performance.Testing; + public class NullTransport : ITransport { public string TransportName { get; set; } = ""; diff --git a/tests/Speckle.Sdk.Performance.Testing/Program.cs b/tests/Speckle.Sdk.Performance.Testing/Program.cs index c21c6f6f..8db75a8e 100644 --- a/tests/Speckle.Sdk.Performance.Testing/Program.cs +++ b/tests/Speckle.Sdk.Performance.Testing/Program.cs @@ -15,14 +15,7 @@ using var dataSource = new TestDataHelper(); await dataSource - .SeedTransport( - new Account() - { - serverInfo = new() { url = url } - }, - "2099ac4b5f", - "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6" - ) + .SeedTransport(new Account() { serverInfo = new() { url = url } }, "2099ac4b5f", "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6") .ConfigureAwait(false); SpeckleObjectDeserializer deserializer = new() { ReadTransport = dataSource.Transport }; diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index 68cff712..476efbdd 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -24,7 +24,7 @@ namespace Speckle.Sdk.Tests.Performance.Benchmarks; public class GeneralSerializerTest { private Base _testData; - + private class Config : ManualConfig { public Config() @@ -38,18 +38,15 @@ public Config() public async Task Setup() { TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); - + var url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small? -//var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? - + //var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? + using var dataSource = new TestDataHelper(); await dataSource .SeedTransport( - new Account() - { - serverInfo = new() { url = url } - }, + new Account() { serverInfo = new() { url = url } }, "2099ac4b5f", "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6" ) @@ -68,7 +65,7 @@ public string RunTest() var x = sut.Serialize(_testData); return x; } - + [Benchmark] public string RunTest2() { From ad39879087af2262caedaea87417acc1a6ccf2b3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 11:55:52 +0100 Subject: [PATCH 06/11] use ValueTasks --- Directory.Packages.props | 1 + src/Speckle.Objects/packages.lock.json | 29 ++++++++++------ .../Api/Operations/Operations.Send.cs | 2 +- src/Speckle.Sdk/Speckle.Sdk.csproj | 1 + src/Speckle.Sdk/Transports/DiskTransport.cs | 33 ++++++++++++++----- src/Speckle.Sdk/Transports/ITransport.cs | 8 ++--- src/Speckle.Sdk/Transports/MemoryTransport.cs | 28 +++++++++++----- src/Speckle.Sdk/Transports/SQLiteTransport.cs | 15 +++++---- src/Speckle.Sdk/Transports/ServerTransport.cs | 8 ++--- .../Transports/TransportHelpers.cs | 2 +- src/Speckle.Sdk/Transports/Utilities.cs | 2 +- src/Speckle.Sdk/packages.lock.json | 23 ++++++++----- .../packages.lock.json | 9 ++++- .../NullTransport.cs | 10 +++--- .../packages.lock.json | 14 ++++---- .../TestTransport.cs | 8 ++--- .../packages.lock.json | 9 ++++- .../packages.lock.json | 9 ++++- .../Benchmarks/GeneralDeserializerTest.cs | 7 ++++ .../Benchmarks/GeneralSerializerTest.cs | 10 +++--- .../packages.lock.json | 14 ++++---- .../Speckle.Sdk.Tests.Unit/packages.lock.json | 9 ++++- 22 files changed, 169 insertions(+), 82 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cade34c9..09f082e2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -26,6 +26,7 @@ + diff --git a/src/Speckle.Objects/packages.lock.json b/src/Speckle.Objects/packages.lock.json index a27ad430..601bfa28 100644 --- a/src/Speckle.Objects/packages.lock.json +++ b/src/Speckle.Objects/packages.lock.json @@ -222,14 +222,6 @@ "System.Runtime": "4.3.0" } }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, "speckle.sdk": { "type": "Project", "dependencies": { @@ -243,7 +235,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "GraphQL.Client": { @@ -332,6 +325,15 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } } }, "net8.0": { @@ -513,7 +515,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "GraphQL.Client": { @@ -599,6 +602,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs index 7670484e..4f55c302 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs @@ -142,7 +142,7 @@ internal static async Task SerializerSend( ) { string obj = serializer.Serialize(value); - Task[] transportAwaits = serializer.WriteTransports.Select(t => t.WriteComplete()).ToArray(); + Task[] transportAwaits = serializer.WriteTransports.Select(t => t.WriteComplete().AsTask()).ToArray(); cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Speckle.Sdk/Speckle.Sdk.csproj b/src/Speckle.Sdk/Speckle.Sdk.csproj index fcd44aaa..ab198be1 100644 --- a/src/Speckle.Sdk/Speckle.Sdk.csproj +++ b/src/Speckle.Sdk/Speckle.Sdk.csproj @@ -44,6 +44,7 @@ + diff --git a/src/Speckle.Sdk/Transports/DiskTransport.cs b/src/Speckle.Sdk/Transports/DiskTransport.cs index 2b543e15..f4e066c3 100644 --- a/src/Speckle.Sdk/Transports/DiskTransport.cs +++ b/src/Speckle.Sdk/Transports/DiskTransport.cs @@ -59,17 +59,24 @@ public void BeginWrite() public void EndWrite() { } - public Task GetObject(string id) + public ValueTask GetObject(string id) { CancellationToken.ThrowIfCancellationRequested(); var filePath = Path.Combine(RootPath, id); if (File.Exists(filePath)) { - return Task.FromResult(File.ReadAllText(filePath, Encoding.UTF8)); +#if NETSTANDARD2_0 + return new ValueTask(File.ReadAllText(filePath, Encoding.UTF8)); +#else + return ValueTask.FromResult(File.ReadAllText(filePath, Encoding.UTF8)); +#endif } - - return Task.FromResult(null); +#if NETSTANDARD2_0 + return new ValueTask((string?)null); +#else + return ValueTask.FromResult(null); +#endif } public void SaveObject(string id, string serializedObject) @@ -98,12 +105,16 @@ public void SaveObject(string id, string serializedObject) Elapsed += stopwatch.Elapsed; } - public Task WriteComplete() + public ValueTask WriteComplete() { - return Task.CompletedTask; +#if NETSTANDARD2_0 + return new ValueTask(Task.CompletedTask); +#else + return ValueTask.CompletedTask; +#endif } - public async Task CopyObjectAndChildren( + public async ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null @@ -115,7 +126,7 @@ public async Task CopyObjectAndChildren( return res; } - public Task> HasObjects(IReadOnlyList objectIds) + public ValueTask> HasObjects(IReadOnlyList objectIds) { Dictionary ret = new(); foreach (string objectId in objectIds) @@ -123,7 +134,11 @@ public Task> HasObjects(IReadOnlyList objectIds var filePath = Path.Combine(RootPath, objectId); ret[objectId] = File.Exists(filePath); } - return Task.FromResult(ret); +#if NETSTANDARD2_0 + return new ValueTask>(ret); +#else + return ValueTask.FromResult(ret); +#endif } public override string ToString() diff --git a/src/Speckle.Sdk/Transports/ITransport.cs b/src/Speckle.Sdk/Transports/ITransport.cs index 5fe1c4b8..5485c658 100644 --- a/src/Speckle.Sdk/Transports/ITransport.cs +++ b/src/Speckle.Sdk/Transports/ITransport.cs @@ -67,12 +67,12 @@ public interface ITransport /// Awaitable method to figure out whether writing is completed. /// /// - public Task WriteComplete(); + public ValueTask WriteComplete(); /// The object's hash. /// The serialized object data, or if the transport cannot find the object /// requested cancel - public Task GetObject(string id); + public ValueTask GetObject(string id); /// /// Copies the parent object and all its children to the provided transport. @@ -84,7 +84,7 @@ public interface ITransport /// The provided arguments are not valid /// The transport could not complete the operation /// requested cancel - public Task CopyObjectAndChildren( + public ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null @@ -97,7 +97,7 @@ public Task CopyObjectAndChildren( /// A dictionary with the specified object ids as keys and boolean values, whether each object is present in the transport or not /// The transport could not complete the operation /// requested cancel - public Task> HasObjects(IReadOnlyList objectIds); + public ValueTask> HasObjects(IReadOnlyList objectIds); } public interface IBlobCapableTransport diff --git a/src/Speckle.Sdk/Transports/MemoryTransport.cs b/src/Speckle.Sdk/Transports/MemoryTransport.cs index f3825c7a..b82f052c 100644 --- a/src/Speckle.Sdk/Transports/MemoryTransport.cs +++ b/src/Speckle.Sdk/Transports/MemoryTransport.cs @@ -90,16 +90,21 @@ public void SaveObject(string id, string serializedObject) Elapsed += stopwatch.Elapsed; } - public Task GetObject(string id) + public ValueTask GetObject(string id) { var stopwatch = Stopwatch.StartNew(); var ret = Objects.TryGetValue(id, out string? o) ? o : null; stopwatch.Stop(); Elapsed += stopwatch.Elapsed; - return Task.FromResult(ret); + +#if NETSTANDARD2_0 + return new ValueTask(ret); +#else + return ValueTask.FromResult(ret); +#endif } - public async Task CopyObjectAndChildren( + public async ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null @@ -111,20 +116,27 @@ public async Task CopyObjectAndChildren( return res; } - public Task WriteComplete() + public ValueTask WriteComplete() { - return Task.CompletedTask; +#if NETSTANDARD2_0 + return new ValueTask(Task.CompletedTask); +#else + return ValueTask.CompletedTask; +#endif } - public Task> HasObjects(IReadOnlyList objectIds) + public ValueTask> HasObjects(IReadOnlyList objectIds) { Dictionary ret = new(objectIds.Count); foreach (string objectId in objectIds) { ret[objectId] = Objects.ContainsKey(objectId); } - - return Task.FromResult(ret); +#if NETSTANDARD2_0 + return new ValueTask>(ret); +#else + return ValueTask.FromResult(ret); +#endif } public override string ToString() diff --git a/src/Speckle.Sdk/Transports/SQLiteTransport.cs b/src/Speckle.Sdk/Transports/SQLiteTransport.cs index d0f8f732..01f5c753 100644 --- a/src/Speckle.Sdk/Transports/SQLiteTransport.cs +++ b/src/Speckle.Sdk/Transports/SQLiteTransport.cs @@ -129,7 +129,7 @@ public void BeginWrite() public void EndWrite() { } - public Task> HasObjects(IReadOnlyList objectIds) + public ValueTask> HasObjects(IReadOnlyList objectIds) { Dictionary ret = new(objectIds.Count); // Initialize with false so that canceled queries still return a dictionary item for every object id @@ -159,8 +159,11 @@ public Task> HasObjects(IReadOnlyList objectIds { throw new TransportException("SQLite transport failed", ex); } - - return Task.FromResult(ret); +#if NETSTANDARD2_0 + return new ValueTask>(ret); +#else + return ValueTask.FromResult(ret); +#endif } /// Failed to initialize connection to the SQLite DB @@ -272,7 +275,7 @@ public override string ToString() /// Awaits untill write completion (ie, the current queue is fully consumed). /// /// - public async Task WriteComplete() => + public async ValueTask WriteComplete() => await Utilities.WaitUntil(() => WriteCompletionStatus, 500).ConfigureAwait(false); /// @@ -400,7 +403,7 @@ public void SaveObjectSync(string hash, string serializedObject) /// /// /// - public async Task GetObject(string id) + public async ValueTask GetObject(string id) { CancellationToken.ThrowIfCancellationRequested(); await _connectionLock.WaitAsync(CancellationToken).ConfigureAwait(false); @@ -427,7 +430,7 @@ public void SaveObjectSync(string hash, string serializedObject) return null; // pass on the duty of null checks to consumers } - public async Task CopyObjectAndChildren( + public async ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null diff --git a/src/Speckle.Sdk/Transports/ServerTransport.cs b/src/Speckle.Sdk/Transports/ServerTransport.cs index cdd997ca..2c0eca02 100644 --- a/src/Speckle.Sdk/Transports/ServerTransport.cs +++ b/src/Speckle.Sdk/Transports/ServerTransport.cs @@ -120,7 +120,7 @@ public void Dispose() public Action? OnProgressAction { get; set; } public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero; - public async Task CopyObjectAndChildren( + public async ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null @@ -199,7 +199,7 @@ await api.DownloadObjects( return rootObjectJson; } - public async Task GetObject(string id) + public async ValueTask GetObject(string id) { CancellationToken.ThrowIfCancellationRequested(); var stopwatch = Stopwatch.StartNew(); @@ -209,7 +209,7 @@ await api.DownloadObjects( return result; } - public async Task> HasObjects(IReadOnlyList objectIds) + public async ValueTask> HasObjects(IReadOnlyList objectIds) { return await Api.HasObjects(StreamId, objectIds).ConfigureAwait(false); } @@ -241,7 +241,7 @@ public void BeginWrite() _sendingThread.Start(); } - public async Task WriteComplete() + public async ValueTask WriteComplete() { while (true) { diff --git a/src/Speckle.Sdk/Transports/TransportHelpers.cs b/src/Speckle.Sdk/Transports/TransportHelpers.cs index 87705d16..c3ebf96d 100644 --- a/src/Speckle.Sdk/Transports/TransportHelpers.cs +++ b/src/Speckle.Sdk/Transports/TransportHelpers.cs @@ -5,7 +5,7 @@ namespace Speckle.Sdk.Transports; public static class TransportHelpers { - public static async Task CopyObjectAndChildrenAsync( + public static async ValueTask CopyObjectAndChildrenAsync( string id, ITransport sourceTransport, ITransport targetTransport, diff --git a/src/Speckle.Sdk/Transports/Utilities.cs b/src/Speckle.Sdk/Transports/Utilities.cs index 3fe06faf..478b3138 100644 --- a/src/Speckle.Sdk/Transports/Utilities.cs +++ b/src/Speckle.Sdk/Transports/Utilities.cs @@ -8,7 +8,7 @@ public static class Utilities /// /// /// - public static async Task WaitUntil(Func condition, int frequency = 25) + public static async ValueTask WaitUntil(Func condition, int frequency = 25) { while (!condition()) { diff --git a/src/Speckle.Sdk/packages.lock.json b/src/Speckle.Sdk/packages.lock.json index e2fe848d..741121d0 100644 --- a/src/Speckle.Sdk/packages.lock.json +++ b/src/Speckle.Sdk/packages.lock.json @@ -126,6 +126,15 @@ "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" }, + "System.Threading.Tasks.Extensions": { + "type": "Direct", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, "GraphQL.Client.Abstractions": { "type": "Transitive", "resolved": "6.0.0", @@ -308,14 +317,6 @@ "dependencies": { "System.Runtime": "4.3.0" } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } } }, "net8.0": { @@ -431,6 +432,12 @@ "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" }, + "System.Threading.Tasks.Extensions": { + "type": "Direct", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, "GraphQL.Client.Abstractions": { "type": "Transitive", "resolved": "6.0.0", diff --git a/tests/Speckle.Objects.Tests.Unit/packages.lock.json b/tests/Speckle.Objects.Tests.Unit/packages.lock.json index 7b07061d..587ef8f9 100644 --- a/tests/Speckle.Objects.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Objects.Tests.Unit/packages.lock.json @@ -283,7 +283,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "GraphQL.Client": { @@ -369,6 +370,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs b/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs index 3c348c92..2dd973a6 100644 --- a/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs +++ b/tests/Speckle.Sdk.Performance.Testing/NullTransport.cs @@ -16,19 +16,19 @@ public void EndWrite() { } public void SaveObject(string id, string serializedObject) { } - public Task WriteComplete() + public ValueTask WriteComplete() { - return Task.CompletedTask; + return ValueTask.CompletedTask; } - public Task GetObject(string id) => throw new NotImplementedException(); + public ValueTask GetObject(string id) => throw new NotImplementedException(); - public Task CopyObjectAndChildren( + public ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null ) => throw new NotImplementedException(); - public Task> HasObjects(IReadOnlyList objectIds) => + public ValueTask> HasObjects(IReadOnlyList objectIds) => throw new NotImplementedException(); } diff --git a/tests/Speckle.Sdk.Performance.Testing/packages.lock.json b/tests/Speckle.Sdk.Performance.Testing/packages.lock.json index ae920033..09ff7039 100644 --- a/tests/Speckle.Sdk.Performance.Testing/packages.lock.json +++ b/tests/Speckle.Sdk.Performance.Testing/packages.lock.json @@ -319,11 +319,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.2" } }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" - }, "speckle.objects": { "type": "Project", "dependencies": { @@ -343,7 +338,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "speckle.sdk.tests.performance": { @@ -464,6 +460,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs b/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs index d4c6de37..d159d669 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs @@ -30,16 +30,16 @@ public string TransportName public void SaveObject(string id, string serializedObject) => Objects[id] = serializedObject; - public Task WriteComplete() => throw new NotImplementedException(); + public ValueTask WriteComplete() => throw new NotImplementedException(); - public Task GetObject(string id) => Task.FromResult(Objects.TryGetValue(id, out string? o) ? o : null); + public ValueTask GetObject(string id) => ValueTask.FromResult(Objects.TryGetValue(id, out string? o) ? o : null); - public Task CopyObjectAndChildren( + public ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action? onTotalChildrenCountKnown = null ) => throw new NotImplementedException(); - public Task> HasObjects(IReadOnlyList objectIds) => + public ValueTask> HasObjects(IReadOnlyList objectIds) => throw new NotImplementedException(); } diff --git a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json index 7b07061d..587ef8f9 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json @@ -283,7 +283,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "GraphQL.Client": { @@ -369,6 +370,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json index 5d3dbf4e..491c9872 100644 --- a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json @@ -277,7 +277,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "speckle.sdk.tests.unit": { @@ -385,6 +386,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs index a14ec73c..b64ddf4a 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs @@ -43,6 +43,13 @@ public async Task RunTest() return await sut.DeserializeAsync(data); } + [Benchmark] + public async Task RunTest2() + { + SpeckleObjectDeserializer sut = new() { ReadTransport = _dataSource.Transport }; + string data = await _dataSource.Transport.GetObject(_dataSource.ObjectId)!; + return await sut.DeserializeAsync(data); + } [GlobalCleanup] public void Cleanup() { diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index 476efbdd..e6308fea 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -90,19 +90,19 @@ public void EndWrite() { } public void SaveObject(string id, string serializedObject) { } - public Task WriteComplete() + public ValueTask WriteComplete() { - return Task.CompletedTask; + return ValueTask.CompletedTask; } - public Task GetObject(string id) => throw new NotImplementedException(); + public ValueTask GetObject(string id) => throw new NotImplementedException(); - public Task CopyObjectAndChildren( + public ValueTask CopyObjectAndChildren( string id, ITransport targetTransport, Action onTotalChildrenCountKnown = null ) => throw new NotImplementedException(); - public Task> HasObjects(IReadOnlyList objectIds) => + public ValueTask> HasObjects(IReadOnlyList objectIds) => throw new NotImplementedException(); } diff --git a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json index 7b6fa582..0889d65f 100644 --- a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json @@ -346,11 +346,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.2" } }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" - }, "speckle.objects": { "type": "Project", "dependencies": { @@ -370,7 +365,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "GraphQL.Client": { @@ -456,6 +452,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json index b19f5925..c07ced15 100644 --- a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json @@ -292,7 +292,8 @@ "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )", + "System.Threading.Tasks.Extensions": "[4.5.4, )" } }, "GraphQL.Client": { @@ -372,6 +373,12 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "System.Threading.Tasks.Extensions": { + "type": "CentralTransitive", + "requested": "[4.5.4, )", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } From cf6228e90f10bc4b2fd0e387dbe7d127625b8ea8 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 12:13:32 +0100 Subject: [PATCH 07/11] add deserializer2 to have to do both --- Directory.Packages.props | 2 +- src/Speckle.Objects/packages.lock.json | 43 ++- .../Helpers/SpeckleObjectSerializer2Pool.cs | 16 + .../SpeckleObjectDeserializer2.cs | 352 ++++++++++++++++++ src/Speckle.Sdk/Speckle.Sdk.csproj | 2 +- src/Speckle.Sdk/packages.lock.json | 35 +- .../packages.lock.json | 16 +- .../packages.lock.json | 21 +- .../packages.lock.json | 16 +- .../packages.lock.json | 16 +- .../Benchmarks/GeneralDeserializerTest.cs | 7 +- .../packages.lock.json | 21 +- .../Speckle.Sdk.Tests.Unit/packages.lock.json | 16 +- 13 files changed, 477 insertions(+), 86 deletions(-) create mode 100644 src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer2.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 09f082e2..6d1dd95c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + @@ -26,7 +27,6 @@ - diff --git a/src/Speckle.Objects/packages.lock.json b/src/Speckle.Objects/packages.lock.json index 601bfa28..a9e35e79 100644 --- a/src/Speckle.Objects/packages.lock.json +++ b/src/Speckle.Objects/packages.lock.json @@ -222,6 +222,14 @@ "System.Runtime": "4.3.0" } }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, "speckle.sdk": { "type": "Project", "dependencies": { @@ -230,13 +238,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "GraphQL.Client": { @@ -284,6 +292,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -325,15 +339,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } } }, "net8.0": { @@ -510,13 +515,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "GraphQL.Client": { @@ -564,6 +569,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -602,12 +613,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs index 449e70ff..1d16213d 100644 --- a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs +++ b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs @@ -1,4 +1,5 @@ using System.Buffers; +using Microsoft.Extensions.ObjectPool; using Microsoft.IO; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; @@ -38,4 +39,19 @@ private class SerializerPool(ArrayPool pool) : IArrayPool public void Return(T[]? array) => pool.Return(array.NotNull()); } + + + public ObjectPool> + DictPool { get; }= new DefaultObjectPoolProvider().Create(new DictPoolPolicy()); + + private class DictPoolPolicy : PooledObjectPolicy> + { + public override Dictionary Create() => new Dictionary(StringComparer.OrdinalIgnoreCase); + + public override bool Return(Dictionary obj) + { + obj.Clear(); + return true; + } + } } diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer2.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer2.cs new file mode 100644 index 00000000..03dec3c0 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer2.cs @@ -0,0 +1,352 @@ +using System.Collections.Concurrent; +using System.Numerics; +using System.Reflection; +using Speckle.Newtonsoft.Json; +using Speckle.Sdk.Common; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation.Utilities; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Serialisation; + +public sealed class SpeckleObjectDeserializer2 +{ + public SpeckleObjectSerializer2Pool Pool { get; } = SpeckleObjectSerializer2Pool.Instance; + private bool _isBusy; + private readonly object _callbackLock = new(); + private readonly object?[] _invokeNull = [null]; + + // id -> Base if already deserialized or id -> Task if was handled by a bg thread + private ConcurrentDictionary? _deserializedObjects; + + /// + /// Property that describes the type of the object. + /// + private const string TYPE_DISCRIMINATOR = nameof(Base.speckle_type); + + public CancellationToken CancellationToken { get; set; } + + /// + /// The sync transport. This transport will be used synchronously. + /// + public ITransport ReadTransport { get; set; } + + public Action? OnProgressAction { get; set; } + + private long _currentCount; + private readonly HashSet _ids = new(); + private long _processedCount; + + public string? BlobStorageFolder { get; set; } + + /// The JSON string of the object to be deserialized + /// A typed object deserialized from the + /// Thrown when + /// was null + /// cannot be deserialised to type + // /// did not contain the required json objects (closures) + public async ValueTask DeserializeAsync(string rootObjectJson) + { + if (_isBusy) + { + throw new InvalidOperationException( + "A deserializer instance can deserialize only 1 object at a time. Consider creating multiple deserializer instances" + ); + } + + try + { + _isBusy = true; + _deserializedObjects = new(StringComparer.Ordinal); + _currentCount = 0; + return (Base)(await DeserializeJsonAsyncInternal(rootObjectJson).NotNull().ConfigureAwait(false)); + } + finally + { + _deserializedObjects = null; + _isBusy = false; + } + } + + private async ValueTask DeserializeJsonAsyncInternal(string objectJson) + { + if (objectJson is null) + { + throw new ArgumentNullException(nameof(objectJson), $"Cannot deserialize {nameof(objectJson)}, value was null"); + } + // Apparently this automatically parses DateTimes in strings if it matches the format: + // JObject doc1 = JObject.Parse(objectJson); + + // This is equivalent code that doesn't parse datetimes: + using JsonReader reader = Pool.GetJsonTextReader(new StringReader(objectJson)); + + reader.DateParseHandling = DateParseHandling.None; + + object? converted; + try + { + reader.Read(); + converted = await ReadObjectAsync(reader, CancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) + { + throw new SpeckleDeserializeException($"Failed to deserialize", ex); + } + + lock (_callbackLock) + { + _processedCount++; + OnProgressAction?.Invoke( + new ProgressArgs(ProgressEvent.DeserializeObject, _currentCount, _ids.Count, _processedCount) + ); + } + + return converted; + } + + //this should be buffered + private async ValueTask> ReadArrayAsync(JsonReader reader, CancellationToken ct) + { + reader.Read(); + List retList = new(); + while (reader.TokenType != JsonToken.EndArray) + { + object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); + if (convertedValue is DataChunk chunk) + { + retList.AddRange(chunk.data); + } + else + { + retList.Add(convertedValue); + } + reader.Read(); //goes to next + } + return retList; + } + + private async ValueTask ReadObjectAsync(JsonReader reader, CancellationToken ct) + { + reader.Read(); + Dictionary dict = Pool.DictPool.Get(); + while (reader.TokenType != JsonToken.EndObject) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + { + string propName = (reader.Value?.ToString()).NotNull(); + if (propName == "__closure") + { + reader.Read(); //goes to prop value + var closures = ClosureParser.GetClosures(reader); + foreach (var closure in closures) + { + _ids.Add(closure.Item1); + } + + foreach (var closure in closures) + { + string objId = closure.Item1; + //don't do anything with return value but later check if null + // https://linear.app/speckle/issue/CXPLA-54/when-deserializing-dont-allow-closures-that-arent-downloadable + await TryGetDeserializedAsync(objId).ConfigureAwait(false); + } + reader.Read(); //goes to next + continue; + } + reader.Read(); //goes prop value + object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); + dict[propName] = convertedValue; + reader.Read(); //goes to next + } + break; + default: + throw new InvalidOperationException($"Unknown {reader.ValueType} with {reader.Value}"); + } + } + + if (!dict.TryGetValue(TYPE_DISCRIMINATOR, out object? speckleType)) + { + return dict; + } + + if (speckleType as string == "reference" && dict.TryGetValue("referencedId", out object? referencedId)) + { + var objId = (string)referencedId.NotNull(); + object? deserialized = await TryGetDeserializedAsync(objId).ConfigureAwait(false); + return deserialized; + } + + var b = Dict2Base(dict); + Pool.DictPool.Return(dict); + return b; + } + + private async ValueTask TryGetDeserializedAsync(string objId) + { + object? deserialized = null; + _deserializedObjects.NotNull(); + if (_deserializedObjects.TryGetValue(objId, out object? o)) + { + deserialized = o; + } + + if (deserialized is ValueTask valueTask) + { + try + { + deserialized = valueTask.Result; + } + catch (AggregateException ex) + { + throw new SpeckleDeserializeException("Failed to deserialize reference object", ex); + } + + if (_deserializedObjects.TryAdd(objId, deserialized)) + { + _currentCount++; + } + } + if (deserialized is Task task) + { + try + { + deserialized = task.Result; + } + catch (AggregateException ex) + { + throw new SpeckleDeserializeException("Failed to deserialize reference object", ex); + } + + if (_deserializedObjects.TryAdd(objId, deserialized)) + { + _currentCount++; + } + } + if (deserialized != null) + { + return deserialized; + } + + // This reference was not already deserialized. Do it now in sync mode + string? objectJson = await ReadTransport.GetObject(objId).ConfigureAwait(false); + if (objectJson is null) + { + return null; + } + + deserialized = await DeserializeJsonAsyncInternal(objectJson).ConfigureAwait(false); + + if (_deserializedObjects.NotNull().TryAdd(objId, deserialized)) + { + _currentCount++; + } + + return deserialized; + } + + private async ValueTask ReadPropertyAsync(JsonReader reader, CancellationToken ct) + { + switch (reader.TokenType) + { + case JsonToken.Undefined: + case JsonToken.Null: + case JsonToken.None: + return null; + case JsonToken.Boolean: + return (bool)reader.Value.NotNull(); + case JsonToken.Integer: + if (reader.Value is long longValue) + { + return longValue; + } + if (reader.Value is BigInteger bitInt) + { + // This is behaviour carried over from v2 to facilitate large numbers from Python + // This is quite hacky, as it's a bit questionable exactly what numbers are supported, and with what tolerance + // For this reason, this can be considered undocumented behaviour, and is only for values within the range of a 64bit integer. + return (double)bitInt; + } + + throw new ArgumentException( + $"Found an unsupported integer type {reader.Value?.GetType()} with value {reader.Value}" + ); + case JsonToken.Float: + return (double)reader.Value.NotNull(); + case JsonToken.String: + return (string)reader.Value.NotNull(); + case JsonToken.Date: + return (DateTime)reader.Value.NotNull(); + case JsonToken.StartArray: + return await ReadArrayAsync(reader, ct).ConfigureAwait(false); + case JsonToken.StartObject: + var dict = await ReadObjectAsync(reader, ct).ConfigureAwait(false); + return dict; + + default: + throw new ArgumentException("Json value not supported: " + reader.ValueType); + } + } + + private Base Dict2Base(Dictionary dictObj) + { + string typeName = (string)dictObj[TYPE_DISCRIMINATOR].NotNull(); + Type type = TypeLoader.GetType(typeName); + Base baseObj = (Base)Activator.CreateInstance(type).NotNull(); + + dictObj.Remove(TYPE_DISCRIMINATOR); + dictObj.Remove("__closure"); + + var staticProperties = TypeCache.GetTypeProperties(typeName); + foreach (var entry in dictObj) + { + if (staticProperties.TryGetValue(entry.Key, out PropertyInfo? value) && value.CanWrite) + { + if (entry.Value == null) + { + // Check for JsonProperty(NullValueHandling = NullValueHandling.Ignore) attribute + JsonPropertyAttribute? attr = TypeLoader.GetJsonPropertyAttribute(value); + if (attr is { NullValueHandling: NullValueHandling.Ignore }) + { + continue; + } + } + + Type targetValueType = value.PropertyType; + bool conversionOk = ValueConverter.ConvertValue(targetValueType, entry.Value, out object? convertedValue); + if (conversionOk) + { + value.SetValue(baseObj, convertedValue); + } + else + { + // Cannot convert the value in the json to the static property type + throw new SpeckleDeserializeException( + $"Cannot deserialize {entry.Value?.GetType().FullName} to {targetValueType.FullName}" + ); + } + } + else + { + // No writable property with this name + CallSiteCache.SetValue(entry.Key, baseObj, entry.Value); + } + } + + if (baseObj is Blob bb && BlobStorageFolder != null) + { + bb.filePath = bb.GetLocalDestinationPath(BlobStorageFolder); + } + + var onDeserializedCallbacks = TypeCache.GetOnDeserializedCallbacks(typeName); + foreach (MethodInfo onDeserialized in onDeserializedCallbacks) + { + onDeserialized.Invoke(baseObj, _invokeNull); + } + + return baseObj; + } +} diff --git a/src/Speckle.Sdk/Speckle.Sdk.csproj b/src/Speckle.Sdk/Speckle.Sdk.csproj index ab198be1..8a72687d 100644 --- a/src/Speckle.Sdk/Speckle.Sdk.csproj +++ b/src/Speckle.Sdk/Speckle.Sdk.csproj @@ -29,6 +29,7 @@ + @@ -44,7 +45,6 @@ - diff --git a/src/Speckle.Sdk/packages.lock.json b/src/Speckle.Sdk/packages.lock.json index 741121d0..4a1135a2 100644 --- a/src/Speckle.Sdk/packages.lock.json +++ b/src/Speckle.Sdk/packages.lock.json @@ -53,6 +53,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "Direct", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Direct", "requested": "[3.0.1, )", @@ -126,15 +132,6 @@ "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" }, - "System.Threading.Tasks.Extensions": { - "type": "Direct", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, "GraphQL.Client.Abstractions": { "type": "Transitive", "resolved": "6.0.0", @@ -317,6 +314,14 @@ "dependencies": { "System.Runtime": "4.3.0" } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } } }, "net8.0": { @@ -371,6 +376,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "Direct", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Direct", "requested": "[3.0.1, )", @@ -432,12 +443,6 @@ "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" }, - "System.Threading.Tasks.Extensions": { - "type": "Direct", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" - }, "GraphQL.Client.Abstractions": { "type": "Transitive", "resolved": "6.0.0", diff --git a/tests/Speckle.Objects.Tests.Unit/packages.lock.json b/tests/Speckle.Objects.Tests.Unit/packages.lock.json index 587ef8f9..8d72263b 100644 --- a/tests/Speckle.Objects.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Objects.Tests.Unit/packages.lock.json @@ -278,13 +278,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "GraphQL.Client": { @@ -332,6 +332,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -370,12 +376,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Performance.Testing/packages.lock.json b/tests/Speckle.Sdk.Performance.Testing/packages.lock.json index 09ff7039..e45aa284 100644 --- a/tests/Speckle.Sdk.Performance.Testing/packages.lock.json +++ b/tests/Speckle.Sdk.Performance.Testing/packages.lock.json @@ -319,6 +319,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.2" } }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, "speckle.objects": { "type": "Project", "dependencies": { @@ -333,13 +338,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "speckle.sdk.tests.performance": { @@ -422,6 +427,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -460,12 +471,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json index 587ef8f9..8d72263b 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json @@ -278,13 +278,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "GraphQL.Client": { @@ -332,6 +332,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -370,12 +376,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json index 491c9872..55699d02 100644 --- a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json @@ -272,13 +272,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "speckle.sdk.tests.unit": { @@ -348,6 +348,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -386,12 +392,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs index b64ddf4a..b2797fb7 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs @@ -22,12 +22,15 @@ public class GeneralDeserializer : IDisposable public async Task Setup() { TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); + var url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small? + + //var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? _dataSource = new TestDataHelper(); await _dataSource .SeedTransport( new Account() { - serverInfo = new() { url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e" } + serverInfo = new() { url =url } }, "2099ac4b5f", "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6" @@ -46,7 +49,7 @@ public async Task RunTest() [Benchmark] public async Task RunTest2() { - SpeckleObjectDeserializer sut = new() { ReadTransport = _dataSource.Transport }; + SpeckleObjectDeserializer2 sut = new() { ReadTransport = _dataSource.Transport }; string data = await _dataSource.Transport.GetObject(_dataSource.ObjectId)!; return await sut.DeserializeAsync(data); } diff --git a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json index 0889d65f..57c10730 100644 --- a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json @@ -346,6 +346,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.2" } }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, "speckle.objects": { "type": "Project", "dependencies": { @@ -360,13 +365,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "GraphQL.Client": { @@ -414,6 +419,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -452,12 +463,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } diff --git a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json index c07ced15..8e774e98 100644 --- a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json @@ -287,13 +287,13 @@ "Microsoft.Data.Sqlite": "[7.0.7, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )", "Microsoft.Extensions.Logging": "[2.2.0, )", + "Microsoft.Extensions.ObjectPool": "[8.0.8, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Polly": "[7.2.3, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", "Polly.Extensions.Http": "[3.0.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", - "Speckle.Newtonsoft.Json": "[13.0.2, )", - "System.Threading.Tasks.Extensions": "[4.5.4, )" + "Speckle.Newtonsoft.Json": "[13.0.2, )" } }, "GraphQL.Client": { @@ -341,6 +341,12 @@ "Microsoft.Extensions.Options": "2.2.0" } }, + "Microsoft.Extensions.ObjectPool": { + "type": "CentralTransitive", + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "wnjTFjEvvSbOs3iMfl6CeJcUgPHZMYUB9uAQbGQGxGwVRl4GydNpMSkVntTzoi7AqQeYumU9yDSNeVbpq+ebow==" + }, "Microsoft.IO.RecyclableMemoryStream": { "type": "CentralTransitive", "requested": "[3.0.1, )", @@ -373,12 +379,6 @@ "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" - }, - "System.Threading.Tasks.Extensions": { - "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" } } } From 1c932de979dbea224ba143a73b45cab56cc3219b Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 12:24:30 +0100 Subject: [PATCH 08/11] revert old deserializer --- .../SpeckleObjectDeserializer.cs | 49 +++++++------------ .../Program.cs | 2 +- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs index 83742853..84b99404 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs @@ -45,7 +45,7 @@ public sealed class SpeckleObjectDeserializer /// was null /// cannot be deserialised to type // /// did not contain the required json objects (closures) - public async ValueTask DeserializeAsync(string rootObjectJson) + public async Task DeserializeAsync(string rootObjectJson) { if (_isBusy) { @@ -68,7 +68,7 @@ public async ValueTask DeserializeAsync(string rootObjectJson) } } - private async ValueTask DeserializeJsonAsyncInternal(string objectJson) + private async Task DeserializeJsonAsyncInternal(string objectJson) { if (objectJson is null) { @@ -85,7 +85,7 @@ public async ValueTask DeserializeAsync(string rootObjectJson) object? converted; try { - reader.Read(); + await reader.ReadAsync(CancellationToken).ConfigureAwait(false); converted = await ReadObjectAsync(reader, CancellationToken).ConfigureAwait(false); } catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) @@ -105,9 +105,9 @@ public async ValueTask DeserializeAsync(string rootObjectJson) } //this should be buffered - private async ValueTask> ReadArrayAsync(JsonReader reader, CancellationToken ct) + private async Task> ReadArrayAsync(JsonReader reader, CancellationToken ct) { - reader.Read(); + await reader.ReadAsync(ct).ConfigureAwait(false); List retList = new(); while (reader.TokenType != JsonToken.EndArray) { @@ -120,14 +120,14 @@ public async ValueTask DeserializeAsync(string rootObjectJson) { retList.Add(convertedValue); } - reader.Read(); //goes to next + await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next } return retList; } - private async ValueTask ReadObjectAsync(JsonReader reader, CancellationToken ct) + private async Task ReadObjectAsync(JsonReader reader, CancellationToken ct) { - reader.Read(); + await reader.ReadAsync(ct).ConfigureAwait(false); Dictionary dict = new(); while (reader.TokenType != JsonToken.EndObject) { @@ -138,8 +138,8 @@ public async ValueTask DeserializeAsync(string rootObjectJson) string propName = (reader.Value?.ToString()).NotNull(); if (propName == "__closure") { - reader.Read(); //goes to prop value - var closures = ClosureParser.GetClosures(reader); + await reader.ReadAsync(ct).ConfigureAwait(false); //goes to prop value + var closures = await ClosureParser.GetClosuresAsync(reader).ConfigureAwait(false); foreach (var closure in closures) { _ids.Add(closure.Item1); @@ -152,13 +152,13 @@ public async ValueTask DeserializeAsync(string rootObjectJson) // https://linear.app/speckle/issue/CXPLA-54/when-deserializing-dont-allow-closures-that-arent-downloadable await TryGetDeserializedAsync(objId).ConfigureAwait(false); } - reader.Read(); //goes to next + await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next continue; } - reader.Read(); //goes prop value + await reader.ReadAsync(ct).ConfigureAwait(false); //goes prop value object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); dict[propName] = convertedValue; - reader.Read(); //goes to next + await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next } break; default: @@ -181,7 +181,7 @@ public async ValueTask DeserializeAsync(string rootObjectJson) return Dict2Base(dict); } - private async ValueTask TryGetDeserializedAsync(string objId) + private async Task TryGetDeserializedAsync(string objId) { object? deserialized = null; _deserializedObjects.NotNull(); @@ -190,22 +190,6 @@ public async ValueTask DeserializeAsync(string rootObjectJson) deserialized = o; } - if (deserialized is ValueTask valueTask) - { - try - { - deserialized = valueTask.Result; - } - catch (AggregateException ex) - { - throw new SpeckleDeserializeException("Failed to deserialize reference object", ex); - } - - if (_deserializedObjects.TryAdd(objId, deserialized)) - { - _currentCount++; - } - } if (deserialized is Task task) { try @@ -244,8 +228,9 @@ public async ValueTask DeserializeAsync(string rootObjectJson) return deserialized; } - private async ValueTask ReadPropertyAsync(JsonReader reader, CancellationToken ct) + private async Task ReadPropertyAsync(JsonReader reader, CancellationToken ct) { + ct.ThrowIfCancellationRequested(); switch (reader.TokenType) { case JsonToken.Undefined: @@ -273,7 +258,7 @@ public async ValueTask DeserializeAsync(string rootObjectJson) case JsonToken.Float: return (double)reader.Value.NotNull(); case JsonToken.String: - return (string)reader.Value.NotNull(); + return (string?)reader.Value.NotNull(); case JsonToken.Date: return (DateTime)reader.Value.NotNull(); case JsonToken.StartArray: diff --git a/tests/Speckle.Sdk.Performance.Testing/Program.cs b/tests/Speckle.Sdk.Performance.Testing/Program.cs index 8db75a8e..25a87f2f 100644 --- a/tests/Speckle.Sdk.Performance.Testing/Program.cs +++ b/tests/Speckle.Sdk.Performance.Testing/Program.cs @@ -18,7 +18,7 @@ await dataSource .SeedTransport(new Account() { serverInfo = new() { url = url } }, "2099ac4b5f", "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6") .ConfigureAwait(false); -SpeckleObjectDeserializer deserializer = new() { ReadTransport = dataSource.Transport }; +SpeckleObjectDeserializer2 deserializer = new() { ReadTransport = dataSource.Transport }; string data = await dataSource.Transport.GetObject(dataSource.ObjectId).NotNull().ConfigureAwait(false); var testData = await deserializer.DeserializeAsync(data).ConfigureAwait(false); From 8b35dcf6878b7da27955f4eb9d095c0a7b4fd0a5 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 12:26:42 +0100 Subject: [PATCH 09/11] mostly fmt --- .../Helpers/SpeckleObjectSerializer2Pool.cs | 8 ++++---- .../SpeckleObjectDeserializer.cs | 2 +- src/Speckle.Sdk/Transports/DiskTransport.cs | 2 +- src/Speckle.Sdk/Transports/MemoryTransport.cs | 2 +- .../TestTransport.cs | 3 ++- .../Benchmarks/GeneralDeserializerTest.cs | 20 ++++++++++++++----- .../Benchmarks/GeneralSerializerTest.cs | 1 - 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs index 1d16213d..b9f9e500 100644 --- a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs +++ b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs @@ -40,13 +40,13 @@ private class SerializerPool(ArrayPool pool) : IArrayPool public void Return(T[]? array) => pool.Return(array.NotNull()); } + public ObjectPool> DictPool { get; } = + new DefaultObjectPoolProvider().Create(new DictPoolPolicy()); - public ObjectPool> - DictPool { get; }= new DefaultObjectPoolProvider().Create(new DictPoolPolicy()); - private class DictPoolPolicy : PooledObjectPolicy> { - public override Dictionary Create() => new Dictionary(StringComparer.OrdinalIgnoreCase); + public override Dictionary Create() => + new Dictionary(StringComparer.OrdinalIgnoreCase); public override bool Return(Dictionary obj) { diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs index 84b99404..97e79af2 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs @@ -139,7 +139,7 @@ public async Task DeserializeAsync(string rootObjectJson) if (propName == "__closure") { await reader.ReadAsync(ct).ConfigureAwait(false); //goes to prop value - var closures = await ClosureParser.GetClosuresAsync(reader).ConfigureAwait(false); + var closures = ClosureParser.GetClosures(reader); foreach (var closure in closures) { _ids.Add(closure.Item1); diff --git a/src/Speckle.Sdk/Transports/DiskTransport.cs b/src/Speckle.Sdk/Transports/DiskTransport.cs index f4e066c3..4b049bbc 100644 --- a/src/Speckle.Sdk/Transports/DiskTransport.cs +++ b/src/Speckle.Sdk/Transports/DiskTransport.cs @@ -69,7 +69,7 @@ public void EndWrite() { } #if NETSTANDARD2_0 return new ValueTask(File.ReadAllText(filePath, Encoding.UTF8)); #else - return ValueTask.FromResult(File.ReadAllText(filePath, Encoding.UTF8)); + return ValueTask.FromResult(File.ReadAllText(filePath, Encoding.UTF8)); #endif } #if NETSTANDARD2_0 diff --git a/src/Speckle.Sdk/Transports/MemoryTransport.cs b/src/Speckle.Sdk/Transports/MemoryTransport.cs index b82f052c..78b758c3 100644 --- a/src/Speckle.Sdk/Transports/MemoryTransport.cs +++ b/src/Speckle.Sdk/Transports/MemoryTransport.cs @@ -96,7 +96,7 @@ public void SaveObject(string id, string serializedObject) var ret = Objects.TryGetValue(id, out string? o) ? o : null; stopwatch.Stop(); Elapsed += stopwatch.Elapsed; - + #if NETSTANDARD2_0 return new ValueTask(ret); #else diff --git a/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs b/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs index d159d669..6002a816 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/TestTransport.cs @@ -32,7 +32,8 @@ public string TransportName public ValueTask WriteComplete() => throw new NotImplementedException(); - public ValueTask GetObject(string id) => ValueTask.FromResult(Objects.TryGetValue(id, out string? o) ? o : null); + public ValueTask GetObject(string id) => + ValueTask.FromResult(Objects.TryGetValue(id, out string? o) ? o : null); public ValueTask CopyObjectAndChildren( string id, diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs index b2797fb7..1dad2146 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs @@ -1,5 +1,7 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; using Microsoft.Extensions.Logging.Abstractions; using Speckle.Objects.Geometry; using Speckle.Sdk.Credentials; @@ -12,12 +14,22 @@ namespace Speckle.Sdk.Tests.Performance.Benchmarks; /// /// How many threads on our Deserializer is optimal /// +[Config(typeof(Config))] +[RankColumn] [MemoryDiagnoser] -[SimpleJob(RunStrategy.Monitoring)] public class GeneralDeserializer : IDisposable { private TestDataHelper _dataSource; + private class Config : ManualConfig + { + public Config() + { + var job = Job.ShortRun.WithLaunchCount(0).WithWarmupCount(0).WithIterationCount(1); + AddJob(job); + } + } + [GlobalSetup] public async Task Setup() { @@ -28,10 +40,7 @@ public async Task Setup() _dataSource = new TestDataHelper(); await _dataSource .SeedTransport( - new Account() - { - serverInfo = new() { url =url } - }, + new Account() { serverInfo = new() { url = url } }, "2099ac4b5f", "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6" ) @@ -53,6 +62,7 @@ public async Task RunTest2() string data = await _dataSource.Transport.GetObject(_dataSource.ObjectId)!; return await sut.DeserializeAsync(data); } + [GlobalCleanup] public void Cleanup() { diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index e6308fea..6c4effd8 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -20,7 +20,6 @@ namespace Speckle.Sdk.Tests.Performance.Benchmarks; [Config(typeof(Config))] [RankColumn] [MemoryDiagnoser] -[Orderer(SummaryOrderPolicy.FastestToSlowest)] public class GeneralSerializerTest { private Base _testData; From a8aed65f6b8102f3f193edf31a49b3378b04fcde Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 13:25:17 +0100 Subject: [PATCH 10/11] not better :( --- .../Helpers/SerializerIdWriter2.cs | 14 +++++-------- .../Helpers/SpeckleObjectSerializer2Pool.cs | 21 ++----------------- .../Serialisation/SpeckleObjectSerializer2.cs | 12 +++++------ 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs b/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs index cf2eba75..639d851e 100644 --- a/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs +++ b/src/Speckle.Sdk/Helpers/SerializerIdWriter2.cs @@ -1,5 +1,4 @@ -using System.Text; -using Speckle.Newtonsoft.Json; +using Speckle.Newtonsoft.Json; namespace Speckle.Sdk.Helpers; @@ -9,21 +8,18 @@ public sealed class SerializerIdWriter2 : JsonWriter #pragma warning disable CA2213 private readonly JsonTextWriter _jsonIdWriter; #pragma warning restore CA2213 - private readonly StreamWriter _idWriter; - private readonly MemoryStream _memoryStream; + private readonly StringWriter _idWriter; protected override void Dispose(bool disposing) { base.Dispose(disposing); _idWriter.Dispose(); - _memoryStream.Dispose(); } public SerializerIdWriter2(JsonWriter jsonWriter, SpeckleObjectSerializer2Pool pool) { _jsonWriter = jsonWriter; - _memoryStream = pool.GetMemoryStream(); - _idWriter = new StreamWriter(_memoryStream); + _idWriter = new StringWriter(); _jsonIdWriter = pool.GetJsonTextWriter(_idWriter); } @@ -31,9 +27,9 @@ public string FinishIdWriter() { _jsonIdWriter.WriteEndObject(); _jsonIdWriter.Flush(); - var s = Encoding.UTF8.GetString(_memoryStream.GetBuffer(), 0, (int)_memoryStream.Length); + var json = _idWriter.ToString(); ((IDisposable)_jsonIdWriter).Dispose(); - return s; + return json; } public override void WriteValue(string? value) diff --git a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs index b9f9e500..121762fe 100644 --- a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs +++ b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs @@ -1,6 +1,5 @@ using System.Buffers; using Microsoft.Extensions.ObjectPool; -using Microsoft.IO; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; @@ -12,25 +11,10 @@ public class SpeckleObjectSerializer2Pool private SpeckleObjectSerializer2Pool() { } - public RecyclableMemoryStream GetMemoryStream() => _recyclableMemoryStreamManager.GetStream(); - - public JsonTextWriter GetJsonTextWriter(Stream stream) => new(new StreamWriter(stream)) { ArrayPool = _charPool }; - public JsonTextWriter GetJsonTextWriter(TextWriter writer) => new(writer) { ArrayPool = _charPool }; public JsonTextReader GetJsonTextReader(TextReader reader) => new(reader) { ArrayPool = _charPool }; - private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager = - new( - new RecyclableMemoryStreamManager.Options() - { - LargeBufferMultiple = 1024 * 1024, - MaximumBufferSize = 16 * 1024 * 1024, - MaximumLargePoolFreeBytes = 16 * 1024 * 1024 * 4, - MaximumSmallPoolFreeBytes = 40 * 1024 * 1024 - } - ); - private readonly SerializerPool _charPool = new(ArrayPool.Create(4096, 4096)); private class SerializerPool(ArrayPool pool) : IArrayPool @@ -42,11 +26,10 @@ private class SerializerPool(ArrayPool pool) : IArrayPool public ObjectPool> DictPool { get; } = new DefaultObjectPoolProvider().Create(new DictPoolPolicy()); - + private class DictPoolPolicy : PooledObjectPolicy> { - public override Dictionary Create() => - new Dictionary(StringComparer.OrdinalIgnoreCase); + public override Dictionary Create() => new(StringComparer.OrdinalIgnoreCase); public override bool Return(Dictionary obj) { diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs index 9151af29..d5d22057 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer2.cs @@ -247,12 +247,11 @@ private void SerializeProperty( string id; string json; - using (var memoryStream = Pool.GetMemoryStream()) - using (var writer = new StreamWriter(memoryStream)) + using (var writer = new StringWriter()) { using var jsonWriter = Pool.GetJsonTextWriter(writer); id = SerializeBaseObject(baseObj, jsonWriter, closure); - json = writer.ToString().NotNull(); + json = writer.ToString(); } if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) @@ -276,12 +275,11 @@ private void SerializeProperty( ObjectReference objRef = new() { referencedId = id }; string json2; - using (var memoryStream = Pool.GetMemoryStream()) - using (var writer2 = new StreamWriter(memoryStream)) + using (var writer = new StringWriter()) { - using var jsonWriter2 = Pool.GetJsonTextWriter(writer2); + using var jsonWriter2 = Pool.GetJsonTextWriter(writer); SerializeProperty(objRef, jsonWriter2); - json2 = writer2.ToString().NotNull(); + json2 = writer.ToString(); UpdateParentClosures(id); } From 284cf294f209eeeb84d9d660647b398579d7f4e9 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 13:25:32 +0100 Subject: [PATCH 11/11] fmt --- src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs index 121762fe..7223797c 100644 --- a/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs +++ b/src/Speckle.Sdk/Helpers/SpeckleObjectSerializer2Pool.cs @@ -26,7 +26,7 @@ private class SerializerPool(ArrayPool pool) : IArrayPool public ObjectPool> DictPool { get; } = new DefaultObjectPoolProvider().Create(new DictPoolPolicy()); - + private class DictPoolPolicy : PooledObjectPolicy> { public override Dictionary Create() => new(StringComparer.OrdinalIgnoreCase);