Using Tasks for Deserialization (#143)
* Use a stack channel for deserialization * multi-threaded * add object dictionary pool * more pooling * adjust sqlite transport * format * Optimize IsPropNameValid * object loader first pass * save test * add cache pre check * save better deserialize * mostly works * uses tasks but slower at end * rework to make more sense * add check to avoid multi-deserialize * modify max parallelism * async enqueuing of tasks * switch to more asyncenumerable * fmt * fmt * cleanup sqlite * make ServerObjectManager * revert change * add ability to skip cache check * cache json to know what is loaded * testing * clean up usage * clean up and added new op * Fix exception handling * fixing progress * remove codejam * remove stackchannel * remove console writeline * add cache check shortcut for root object * recevie2 benchmark --------- Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
This commit is contained in:
@@ -8,10 +8,12 @@
|
||||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<!-- Keep at 7 for side by side -->
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.10" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageVersion Include="MongoDB.Driver" Version="2.19.2" />
|
||||
<PackageVersion Include="Moq" Version="4.20.70" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
|
||||
@@ -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.Serialization.Testing", "tests\Speckle.Sdk.Serialization.Testing\Speckle.Sdk.Serialization.Testing.csproj", "{FF922B6D-D416-4348-8CB8-0C8B28691070}"
|
||||
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
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.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}
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -234,10 +234,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -256,6 +258,15 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -290,6 +301,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
@@ -494,10 +511,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -516,6 +535,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -550,6 +575,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
|
||||
@@ -2,12 +2,46 @@ using Microsoft.Extensions.Logging;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
using Speckle.Sdk.Serialisation.V2;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
|
||||
public partial class Operations
|
||||
{
|
||||
public async Task<Base> Receive2(
|
||||
Uri url,
|
||||
string streamId,
|
||||
string objectId,
|
||||
string? authorizationToken = null,
|
||||
IProgress<ProgressArgs>? onProgressAction = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
using var receiveActivity = activityFactory.Start("Operations.Receive");
|
||||
metricsFactory.CreateCounter<long>("Receive").Add(1);
|
||||
|
||||
receiveActivity?.SetTag("objectId", objectId);
|
||||
|
||||
try
|
||||
{
|
||||
var sqliteTransport = new SQLiteCacheManager(streamId);
|
||||
var serverObjects = new ServerObjectManager(speckleHttp, activityFactory, url, authorizationToken);
|
||||
var o = new ObjectLoader(sqliteTransport, serverObjects, streamId, onProgressAction);
|
||||
var process = new DeserializeProcess(onProgressAction, o);
|
||||
var result = await process.Deserialize(objectId, cancellationToken).ConfigureAwait(false);
|
||||
receiveActivity?.SetStatus(SdkActivityStatusCode.Ok);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
receiveActivity?.SetStatus(SdkActivityStatusCode.Error);
|
||||
receiveActivity?.RecordException(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives an object (and all its sub-children) from the two provided <see cref="ITransport"/>s.
|
||||
/// <br/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
@@ -12,6 +13,7 @@ namespace Speckle.Sdk.Api;
|
||||
[GenerateAutoInterface]
|
||||
public partial class Operations(
|
||||
ILogger<Operations> logger,
|
||||
ISpeckleHttp speckleHttp,
|
||||
ISdkActivityFactory activityFactory,
|
||||
ISdkMetricsFactory metricsFactory
|
||||
) : IOperations;
|
||||
|
||||
@@ -66,8 +66,14 @@ public sealed class SpeckleHttpClientHandler : DelegatingHandler
|
||||
|
||||
if (policyResult.Outcome == OutcomeType.Successful)
|
||||
{
|
||||
activity?.SetStatus(SdkActivityStatusCode.Ok);
|
||||
return policyResult.Result.NotNull();
|
||||
}
|
||||
activity?.SetStatus(SdkActivityStatusCode.Error);
|
||||
if (policyResult.FinalException != null)
|
||||
{
|
||||
activity?.RecordException(policyResult.FinalException);
|
||||
}
|
||||
|
||||
// if the policy failed due to a cancellation, AND it was our cancellation token, then don't wrap the exception, and rethrow an new cancellation
|
||||
if (policyResult.FinalException is OperationCanceledException)
|
||||
@@ -75,7 +81,10 @@ public sealed class SpeckleHttpClientHandler : DelegatingHandler
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
throw new HttpRequestException("Policy Failed", policyResult.FinalException);
|
||||
throw new HttpRequestException(
|
||||
"Policy Failed: " + policyResult.FinalHandledResult?.StatusCode ?? "Unknown",
|
||||
policyResult.FinalException
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Buffers;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Common;
|
||||
|
||||
@@ -22,4 +23,31 @@ public class SpeckleObjectSerializerPool
|
||||
|
||||
public void Return(T[]? array) => pool.Return(array.NotNull());
|
||||
}
|
||||
|
||||
public ObjectPool<Dictionary<string, object?>> ObjectDictionaries { get; } =
|
||||
ObjectPool.Create(new ObjectDictionaryPolicy());
|
||||
|
||||
private class ObjectDictionaryPolicy : IPooledObjectPolicy<Dictionary<string, object?>>
|
||||
{
|
||||
public Dictionary<string, object?> Create() => new(50, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool Return(Dictionary<string, object?> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectPool<List<string>> ListString { get; } = ObjectPool.Create(new ListStringPolicy());
|
||||
|
||||
private class ListStringPolicy : IPooledObjectPolicy<List<string>>
|
||||
{
|
||||
public List<string> Create() => new(20);
|
||||
|
||||
public bool Return(List<string> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2;
|
||||
|
||||
public static class AsyncExtensions
|
||||
{
|
||||
public static async ValueTask<TItem> FirstAsync<TItem>(this IAsyncEnumerable<TItem> source)
|
||||
{
|
||||
var e = source.GetAsyncEnumerator();
|
||||
if (await e.MoveNextAsync().ConfigureAwait(false))
|
||||
{
|
||||
return e.Current;
|
||||
}
|
||||
throw new InvalidOperationException("Sequence contains no elements");
|
||||
}
|
||||
|
||||
public static async IAsyncEnumerable<TItem> SelectManyAsync<TItem>(this IEnumerable<IAsyncEnumerable<TItem>> source)
|
||||
{
|
||||
// get enumerators from all inner IAsyncEnumerable
|
||||
var enumerators = source.Select(x => x.GetAsyncEnumerator()).ToList();
|
||||
|
||||
List<Task<(IAsyncEnumerator<TItem>, bool)>> runningTasks = new();
|
||||
|
||||
// start all inner IAsyncEnumerable
|
||||
foreach (var asyncEnumerator in enumerators)
|
||||
{
|
||||
runningTasks.Add(MoveNextWrapped(asyncEnumerator));
|
||||
}
|
||||
|
||||
// while there are any running tasks
|
||||
while (runningTasks.Count != 0)
|
||||
{
|
||||
// get next finished task and remove it from list
|
||||
var finishedTask = await Task.WhenAny(runningTasks).ConfigureAwait(false);
|
||||
runningTasks.Remove(finishedTask);
|
||||
|
||||
// get result from finished IAsyncEnumerable
|
||||
var result = await finishedTask.ConfigureAwait(false);
|
||||
var asyncEnumerator = result.Item1;
|
||||
var hasItem = result.Item2;
|
||||
|
||||
// if IAsyncEnumerable has item, return it and put it back as running for next item
|
||||
if (hasItem)
|
||||
{
|
||||
yield return asyncEnumerator.Current;
|
||||
|
||||
runningTasks.Add(MoveNextWrapped(asyncEnumerator));
|
||||
}
|
||||
}
|
||||
|
||||
// don't forget to dispose, should be in finally
|
||||
foreach (var asyncEnumerator in enumerators)
|
||||
{
|
||||
await asyncEnumerator.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that returns Task with tuple of IAsyncEnumerable and it's result of MoveNextAsync.
|
||||
/// </summary>
|
||||
private static async Task<(IAsyncEnumerator<TItem>, bool)> MoveNextWrapped<TItem>(
|
||||
IAsyncEnumerator<TItem> asyncEnumerator
|
||||
)
|
||||
{
|
||||
var res = await asyncEnumerator.MoveNextAsync().ConfigureAwait(false);
|
||||
return (asyncEnumerator, res);
|
||||
}
|
||||
|
||||
public static IAsyncEnumerable<TSource[]> BatchAsync<TSource>(this IAsyncEnumerable<TSource> source, int size) =>
|
||||
AsyncEnumerableChunkIterator(source, size);
|
||||
|
||||
private static async IAsyncEnumerable<TSource[]> AsyncEnumerableChunkIterator<TSource>(
|
||||
IAsyncEnumerable<TSource> source,
|
||||
int size
|
||||
)
|
||||
{
|
||||
#pragma warning disable CA2007
|
||||
await using IAsyncEnumerator<TSource> e = source.GetAsyncEnumerator();
|
||||
#pragma warning restore CA2007
|
||||
|
||||
// Before allocating anything, make sure there's at least one element.
|
||||
if (await e.MoveNextAsync().ConfigureAwait(false))
|
||||
{
|
||||
// Now that we know we have at least one item, allocate an initial storage array. This is not
|
||||
// the array we'll yield. It starts out small in order to avoid significantly overallocating
|
||||
// when the source has many fewer elements than the chunk size.
|
||||
int arraySize = Math.Min(size, 4);
|
||||
int i;
|
||||
do
|
||||
{
|
||||
var array = new TSource[arraySize];
|
||||
|
||||
// Store the first item.
|
||||
array[0] = e.Current;
|
||||
i = 1;
|
||||
|
||||
if (size != array.Length)
|
||||
{
|
||||
// This is the first chunk. As we fill the array, grow it as needed.
|
||||
for (; i < size && await e.MoveNextAsync().ConfigureAwait(false); i++)
|
||||
{
|
||||
if (i >= array.Length)
|
||||
{
|
||||
arraySize = (int)Math.Min((uint)size, 2 * (uint)array.Length);
|
||||
Array.Resize(ref array, arraySize);
|
||||
}
|
||||
|
||||
array[i] = e.Current;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For all but the first chunk, the array will already be correctly sized.
|
||||
// We can just store into it until either it's full or MoveNext returns false.
|
||||
TSource[] local = array; // avoid bounds checks by using cached local (`array` is lifted to iterator object as a field)
|
||||
Debug.Assert(local.Length == size);
|
||||
for (; (uint)i < (uint)local.Length && await e.MoveNextAsync().ConfigureAwait(false); i++)
|
||||
{
|
||||
local[i] = e.Current;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != array.Length)
|
||||
{
|
||||
Array.Resize(ref array, i);
|
||||
}
|
||||
|
||||
yield return array;
|
||||
} while (i >= size && await e.MoveNextAsync().ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TSource[]> Batch<TSource>(this IEnumerable<TSource> source, int size)
|
||||
{
|
||||
if (source is TSource[] array)
|
||||
{
|
||||
// Special-case arrays, which have an immutable length. This enables us to not only do an
|
||||
// empty check and avoid allocating an iterator object when empty, it enables us to have a
|
||||
// much more efficient (and simpler) implementation for chunking up the array.
|
||||
return array.Length != 0 ? ArrayChunkIterator(array, size) : [];
|
||||
}
|
||||
|
||||
return EnumerableChunkIterator(source, size);
|
||||
}
|
||||
|
||||
private static IEnumerable<TSource[]> ArrayChunkIterator<TSource>(TSource[] source, int size)
|
||||
{
|
||||
int index = 0;
|
||||
while (index < source.Length)
|
||||
{
|
||||
TSource[] chunk = new ReadOnlySpan<TSource>(source, index, Math.Min(size, source.Length - index)).ToArray();
|
||||
index += chunk.Length;
|
||||
yield return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<TSource[]> EnumerableChunkIterator<TSource>(IEnumerable<TSource> source, int size)
|
||||
{
|
||||
using IEnumerator<TSource> e = source.GetEnumerator();
|
||||
|
||||
// Before allocating anything, make sure there's at least one element.
|
||||
if (e.MoveNext())
|
||||
{
|
||||
// Now that we know we have at least one item, allocate an initial storage array. This is not
|
||||
// the array we'll yield. It starts out small in order to avoid significantly overallocating
|
||||
// when the source has many fewer elements than the chunk size.
|
||||
int arraySize = Math.Min(size, 4);
|
||||
int i;
|
||||
do
|
||||
{
|
||||
var array = new TSource[arraySize];
|
||||
|
||||
// Store the first item.
|
||||
array[0] = e.Current;
|
||||
i = 1;
|
||||
|
||||
if (size != array.Length)
|
||||
{
|
||||
// This is the first chunk. As we fill the array, grow it as needed.
|
||||
for (; i < size && e.MoveNext(); i++)
|
||||
{
|
||||
if (i >= array.Length)
|
||||
{
|
||||
arraySize = (int)Math.Min((uint)size, 2 * (uint)array.Length);
|
||||
Array.Resize(ref array, arraySize);
|
||||
}
|
||||
|
||||
array[i] = e.Current;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For all but the first chunk, the array will already be correctly sized.
|
||||
// We can just store into it until either it's full or MoveNext returns false.
|
||||
TSource[] local = array; // avoid bounds checks by using cached local (`array` is lifted to iterator object as a field)
|
||||
Debug.Assert(local.Length == size);
|
||||
for (; (uint)i < (uint)local.Length && e.MoveNext(); i++)
|
||||
{
|
||||
local[i] = e.Current;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != array.Length)
|
||||
{
|
||||
Array.Resize(ref array, i);
|
||||
}
|
||||
|
||||
yield return array;
|
||||
} while (i >= size && e.MoveNext());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
public record DeserializeOptions(bool? SkipCacheCheck = null);
|
||||
|
||||
public sealed class DeserializeProcess(IProgress<ProgressArgs>? progress, IObjectLoader objectLoader)
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, (string, IReadOnlyList<string>)> _closures = new();
|
||||
private long _total;
|
||||
|
||||
public ConcurrentDictionary<string, Base> BaseCache { get; } = new();
|
||||
|
||||
public async Task<Base> Deserialize(
|
||||
string rootId,
|
||||
CancellationToken cancellationToken,
|
||||
DeserializeOptions? options = null
|
||||
)
|
||||
{
|
||||
var (rootJson, childrenIds) = await objectLoader
|
||||
.GetAndCache(rootId, cancellationToken, options)
|
||||
.ConfigureAwait(false);
|
||||
_total = childrenIds.Count;
|
||||
_closures.TryAdd(rootId, (rootJson, childrenIds));
|
||||
progress?.Report(new(ProgressEvent.DeserializeObject, BaseCache.Count, childrenIds.Count));
|
||||
await Traverse(rootId, cancellationToken).ConfigureAwait(false);
|
||||
return BaseCache[rootId];
|
||||
}
|
||||
|
||||
private async Task Traverse(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
if (BaseCache.ContainsKey(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var (_, childIds) = GetClosures(id);
|
||||
var tasks = new List<Task>();
|
||||
foreach (var childId in childIds)
|
||||
{
|
||||
lock (BaseCache)
|
||||
{
|
||||
if (BaseCache.ContainsKey(childId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// tmp is necessary because of the way closures close over loop variables
|
||||
var tmpId = childId;
|
||||
Task t = Task
|
||||
.Factory.StartNew(
|
||||
() => Traverse(tmpId, cancellationToken),
|
||||
cancellationToken,
|
||||
TaskCreationOptions.AttachedToParent,
|
||||
TaskScheduler.Default
|
||||
)
|
||||
.Unwrap();
|
||||
tasks.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.Count > 0)
|
||||
{
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//don't redo things if the id is decoded already in the cache
|
||||
if (!BaseCache.ContainsKey(id))
|
||||
{
|
||||
DecodeOrEnqueueChildren(id);
|
||||
progress?.Report(new(ProgressEvent.DeserializeObject, BaseCache.Count, _total));
|
||||
}
|
||||
}
|
||||
|
||||
private (string, IReadOnlyList<string>) GetClosures(string id)
|
||||
{
|
||||
if (!_closures.TryGetValue(id, out var closures))
|
||||
{
|
||||
var json = objectLoader.LoadId(id);
|
||||
if (json == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
var childrenIds = ClosureParser.GetClosures(json).OrderByDescending(x => x.Item2).Select(x => x.Item1).ToList();
|
||||
closures = (json, childrenIds);
|
||||
_closures.TryAdd(id, closures);
|
||||
}
|
||||
|
||||
return closures;
|
||||
}
|
||||
|
||||
public void DecodeOrEnqueueChildren(string id)
|
||||
{
|
||||
if (BaseCache.ContainsKey(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
(string json, _) = GetClosures(id);
|
||||
var @base = Deserialise(id, json);
|
||||
BaseCache.TryAdd(id, @base);
|
||||
//remove from JSON cache because we've finally made the Base
|
||||
_closures.TryRemove(id, out _);
|
||||
}
|
||||
|
||||
private Base Deserialise(string id, string json)
|
||||
{
|
||||
if (BaseCache.TryGetValue(id, out var baseObject))
|
||||
{
|
||||
return baseObject;
|
||||
}
|
||||
SpeckleObjectDeserializer2 deserializer = new(BaseCache, SpeckleObjectSerializerPool.Instance);
|
||||
return deserializer.Deserialize(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Reflection;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
public static class DictionaryConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Property that describes the type of the object.
|
||||
/// </summary>
|
||||
public const string TYPE_DISCRIMINATOR = nameof(Base.speckle_type);
|
||||
private static readonly object?[] s_invokeNull = [null];
|
||||
|
||||
public static string? BlobStorageFolder { get; set; }
|
||||
|
||||
public static Base Dict2Base(Dictionary<string, object?> dictObj, bool skipInvalidConverts)
|
||||
{
|
||||
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,
|
||||
skipInvalidConverts,
|
||||
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, s_invokeNull);
|
||||
}
|
||||
|
||||
return baseObj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public sealed class ObjectLoader(
|
||||
ISQLiteCacheManager sqLiteCacheManager,
|
||||
IServerObjectManager serverObjectManager,
|
||||
string streamId,
|
||||
IProgress<ProgressArgs>? progress
|
||||
) : IObjectLoader
|
||||
{
|
||||
private const int HTTP_ID_CHUNK_SIZE = 500;
|
||||
private const int CACHE_CHUNK_SIZE = 3000;
|
||||
private const int MAX_PARALLELISM_HTTP = 4;
|
||||
|
||||
public async Task<(string, IReadOnlyList<string>)> GetAndCache(
|
||||
string rootId,
|
||||
CancellationToken cancellationToken,
|
||||
DeserializeOptions? options = null
|
||||
)
|
||||
{
|
||||
var rootJson = sqLiteCacheManager.GetObject(rootId);
|
||||
if (rootJson != null)
|
||||
{
|
||||
//assume everything exists as the root is there.
|
||||
var allChildren = ClosureParser.GetChildrenIds(rootJson).ToList();
|
||||
return (rootJson, allChildren);
|
||||
}
|
||||
rootJson = await serverObjectManager
|
||||
.DownloadSingleObject(streamId, rootId, progress, cancellationToken)
|
||||
.NotNull()
|
||||
.ConfigureAwait(false);
|
||||
var allChildrenIds = ClosureParser
|
||||
.GetClosures(rootJson)
|
||||
.OrderByDescending(x => x.Item2)
|
||||
.Select(x => x.Item1)
|
||||
.Where(x => !x.StartsWith("blob", StringComparison.Ordinal))
|
||||
.ToList();
|
||||
if (!(options?.SkipCacheCheck ?? false))
|
||||
{
|
||||
var idsToDownload = CheckCache(allChildrenIds);
|
||||
await DownloadAndCache(idsToDownload, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
//save the root last to shortcut later
|
||||
sqLiteCacheManager.SaveObjectSync(rootId, rootJson);
|
||||
return (rootJson, allChildrenIds);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<string> CheckCache(IReadOnlyList<string> childrenIds)
|
||||
{
|
||||
var count = 0L;
|
||||
progress?.Report(new(ProgressEvent.CacheCheck, count, childrenIds.Count));
|
||||
await foreach (
|
||||
var (id, result) in childrenIds
|
||||
.Batch(CACHE_CHUNK_SIZE)
|
||||
.Select(x => sqLiteCacheManager.HasObjects2(x)) // there needs to be a Task somewhere here
|
||||
.SelectManyAsync()
|
||||
)
|
||||
{
|
||||
count++;
|
||||
progress?.Report(new(ProgressEvent.CacheCheck, count, childrenIds.Count));
|
||||
if (!result)
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadAndCache(IAsyncEnumerable<string> ids, CancellationToken cancellationToken)
|
||||
{
|
||||
var count = 0L;
|
||||
progress?.Report(new(ProgressEvent.DownloadObject, count, null));
|
||||
var toCache = new List<(string, string)>();
|
||||
var tasks = new ConcurrentBag<Task>();
|
||||
using SemaphoreSlim ss = new(MAX_PARALLELISM_HTTP, MAX_PARALLELISM_HTTP);
|
||||
await foreach (var idBatch in ids.BatchAsync(HTTP_ID_CHUNK_SIZE).WithCancellation(cancellationToken))
|
||||
{
|
||||
await ss.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await foreach (
|
||||
var (id, json) in serverObjectManager.DownloadObjects(streamId, idBatch, progress, cancellationToken)
|
||||
)
|
||||
{
|
||||
count++;
|
||||
progress?.Report(new(ProgressEvent.DownloadObject, count, null));
|
||||
toCache.Add((id, json));
|
||||
if (toCache.Count >= CACHE_CHUNK_SIZE)
|
||||
{
|
||||
var toSave = toCache;
|
||||
toCache = new List<(string, string)>();
|
||||
#pragma warning disable CA2008
|
||||
tasks.Add(
|
||||
Task.Factory.StartNew(() => sqLiteCacheManager.SaveObjects(toSave, cancellationToken), cancellationToken)
|
||||
);
|
||||
#pragma warning restore CA2008
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ss.Release();
|
||||
}
|
||||
}
|
||||
|
||||
if (toCache.Count > 0)
|
||||
{
|
||||
#pragma warning disable CA2008
|
||||
tasks.Add(
|
||||
Task.Factory.StartNew(() => sqLiteCacheManager.SaveObjects(toCache, cancellationToken), cancellationToken)
|
||||
);
|
||||
#pragma warning restore CA2008
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string? LoadId(string id) => sqLiteCacheManager.GetObject(id);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System.Numerics;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
public record DeserializedOptions(bool ThrowOnMissingReferences = true, bool SkipInvalidConverts = false);
|
||||
|
||||
public sealed class SpeckleObjectDeserializer2(
|
||||
IReadOnlyDictionary<string, Base> references,
|
||||
SpeckleObjectSerializerPool pool,
|
||||
DeserializedOptions? options = null
|
||||
)
|
||||
{
|
||||
/// <param name="objectJson">The JSON string of the object to be deserialized <see cref="Base"/></param>
|
||||
/// <returns>A <see cref="Base"/> typed object deserialized from the <paramref name="objectJson"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="objectJson"/> was null</exception>
|
||||
/// <exception cref="SpeckleDeserializeException"><paramref name="objectJson"/> cannot be deserialised to type <see cref="Base"/></exception>
|
||||
// /// <exception cref="TransportException"><see cref="ReadTransport"/> did not contain the required json objects (closures)</exception>
|
||||
public Base Deserialize(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 var stringReader = new StringReader(objectJson);
|
||||
using JsonTextReader reader = pool.GetJsonTextReader(stringReader);
|
||||
|
||||
reader.DateParseHandling = DateParseHandling.None;
|
||||
|
||||
Base? converted;
|
||||
try
|
||||
{
|
||||
reader.Read();
|
||||
converted = (Base)ReadObject(reader).NotNull();
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException)
|
||||
{
|
||||
throw new SpeckleDeserializeException("Failed to deserialize", ex);
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
private List<object?> ReadArrayAsync(JsonReader reader)
|
||||
{
|
||||
reader.Read();
|
||||
List<object?> retList = new();
|
||||
while (reader.TokenType != JsonToken.EndArray)
|
||||
{
|
||||
object? convertedValue = ReadProperty(reader);
|
||||
if (convertedValue is DataChunk chunk)
|
||||
{
|
||||
retList.AddRange(chunk.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
retList.Add(convertedValue);
|
||||
}
|
||||
reader.Read(); //goes to next
|
||||
}
|
||||
return retList;
|
||||
}
|
||||
|
||||
private object? ReadObject(JsonReader reader)
|
||||
{
|
||||
reader.Read();
|
||||
Dictionary<string, object?> dict = pool.ObjectDictionaries.Get();
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
{
|
||||
var propName = reader.Value.NotNull().ToString().NotNull();
|
||||
reader.Read(); //goes prop value
|
||||
object? convertedValue = ReadProperty(reader);
|
||||
dict[propName] = convertedValue;
|
||||
reader.Read(); //goes to next
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown {reader.ValueType} with {reader.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!dict.TryGetValue(DictionaryConverter.TYPE_DISCRIMINATOR, out object? speckleType))
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
if (speckleType as string == "reference" && dict.TryGetValue("referencedId", out object? referencedId))
|
||||
{
|
||||
var objId = (string)referencedId.NotNull();
|
||||
if (references.TryGetValue(objId, out Base? closure))
|
||||
{
|
||||
return closure;
|
||||
}
|
||||
|
||||
if (options is null || options.ThrowOnMissingReferences)
|
||||
{
|
||||
throw new InvalidOperationException($"missing reference: {objId}");
|
||||
}
|
||||
//since we don't throw on missing references, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
var b = DictionaryConverter.Dict2Base(dict, options?.SkipInvalidConverts ?? false);
|
||||
pool.ObjectDictionaries.Return(dict);
|
||||
return b;
|
||||
}
|
||||
|
||||
private object? ReadProperty(JsonReader reader)
|
||||
{
|
||||
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 ReadArrayAsync(reader);
|
||||
case JsonToken.StartObject:
|
||||
var dict = ReadObject(reader);
|
||||
return dict;
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Json value not supported: " + reader.ValueType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Sdk.Common;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
internal static class ValueConverter
|
||||
{
|
||||
private static readonly object[] s_singleValue = new object[1];
|
||||
|
||||
[SuppressMessage(
|
||||
"Maintainability",
|
||||
"CA1502:Avoid excessive complexity",
|
||||
Justification = "To fix this requires rewrite of serializaiton"
|
||||
)]
|
||||
public static bool ConvertValue(Type type, object? value, bool skipInvalidConverts, out object? convertedValue)
|
||||
{
|
||||
// TODO: Document list of supported values in the SDK. (and grow it as needed)
|
||||
|
||||
convertedValue = null;
|
||||
if (value == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Type valueType = value.GetType();
|
||||
|
||||
if (type.IsAssignableFrom(valueType))
|
||||
{
|
||||
convertedValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
//strings
|
||||
if (type == typeof(string))
|
||||
{
|
||||
convertedValue = Convert.ToString(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Enum
|
||||
if (type.IsEnum)
|
||||
{
|
||||
if (valueType != typeof(long))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedValue = Enum.ToObject(type, (long)value);
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
switch (type.Name)
|
||||
{
|
||||
case "Nullable`1":
|
||||
return ConvertValue(type.GenericTypeArguments[0], value, skipInvalidConverts, out convertedValue);
|
||||
#region Numbers
|
||||
case "Int64":
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (long)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case "Int32":
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (int)(long)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case "Int16":
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (short)(long)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case "UInt64":
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (ulong)(long)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case "UInt32":
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (uint)(long)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case "UInt16":
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (ushort)(long)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case "Double":
|
||||
if (valueType == typeof(double))
|
||||
{
|
||||
convertedValue = (double)value;
|
||||
return true;
|
||||
}
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (double)(long)value;
|
||||
return true;
|
||||
}
|
||||
switch (value)
|
||||
{
|
||||
case "NaN":
|
||||
convertedValue = double.NaN;
|
||||
return true;
|
||||
case "Infinity":
|
||||
convertedValue = double.PositiveInfinity;
|
||||
return true;
|
||||
case "-Infinity":
|
||||
convertedValue = double.NegativeInfinity;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case "Single":
|
||||
if (valueType == typeof(double))
|
||||
{
|
||||
convertedValue = (float)(double)value;
|
||||
return true;
|
||||
}
|
||||
if (valueType == typeof(long))
|
||||
{
|
||||
convertedValue = (float)(long)value;
|
||||
return true;
|
||||
}
|
||||
switch (value)
|
||||
{
|
||||
case "NaN":
|
||||
convertedValue = float.NaN;
|
||||
return true;
|
||||
case "Infinity":
|
||||
convertedValue = float.PositiveInfinity;
|
||||
return true;
|
||||
case "-Infinity":
|
||||
convertedValue = float.NegativeInfinity;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// Handle List<>, IList<>, and IReadOnlyList<>
|
||||
if (type.IsGenericType && IsGenericList(type))
|
||||
{
|
||||
if (value is not List<object> valueList)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetType = typeof(List<>).MakeGenericType(type.GenericTypeArguments);
|
||||
Type listElementType = type.GenericTypeArguments[0];
|
||||
|
||||
s_singleValue[0] = valueList.Count;
|
||||
//reuse array to avoid params array allocation
|
||||
IList ret = (IList)Activator.CreateInstance(targetType, s_singleValue).NotNull();
|
||||
|
||||
foreach (object inputListElement in valueList)
|
||||
{
|
||||
if (!ConvertValue(listElementType, inputListElement, skipInvalidConverts, out object? convertedListElement))
|
||||
{
|
||||
if (skipInvalidConverts)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ret.Add(convertedListElement);
|
||||
}
|
||||
convertedValue = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle Dictionary<string,?>
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
if (value is not Dictionary<string, object> valueDict)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.GenericTypeArguments[0] != typeof(string))
|
||||
{
|
||||
throw new ArgumentException("Dictionaries with non-string keys are not supported", nameof(type));
|
||||
}
|
||||
|
||||
Type dictValueType = type.GenericTypeArguments[1];
|
||||
IDictionary ret = (IDictionary)Activator.CreateInstance(type).NotNull();
|
||||
|
||||
foreach (KeyValuePair<string, object> kv in valueDict)
|
||||
{
|
||||
if (!ConvertValue(dictValueType, kv.Value, skipInvalidConverts, out object? convertedDictValue))
|
||||
{
|
||||
if (skipInvalidConverts)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ret[kv.Key] = convertedDictValue;
|
||||
}
|
||||
convertedValue = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (type.IsArray)
|
||||
{
|
||||
if (value is not List<object> valueList)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type arrayElementType =
|
||||
type.GetElementType() ?? throw new ArgumentException("IsArray yet not valid element type", nameof(type));
|
||||
|
||||
Array ret = Array.CreateInstance(arrayElementType, valueList.Count);
|
||||
for (int i = 0; i < valueList.Count; i++)
|
||||
{
|
||||
object inputListElement = valueList[i];
|
||||
if (!ConvertValue(arrayElementType, inputListElement, skipInvalidConverts, out object? convertedListElement))
|
||||
{
|
||||
if (skipInvalidConverts)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ret.SetValue(convertedListElement, i);
|
||||
}
|
||||
convertedValue = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle simple classes/structs
|
||||
if (type == typeof(Guid) && value is string str)
|
||||
{
|
||||
convertedValue = Guid.Parse(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == typeof(Color) && value is long integer)
|
||||
{
|
||||
convertedValue = Color.FromArgb((int)integer);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == typeof(DateTime) && value is string s)
|
||||
{
|
||||
convertedValue = DateTime.ParseExact(s, "o", CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region BACKWARDS COMPATIBILITY: matrix4x4 changed from System.Numerics float to System.DoubleNumerics double in release 2.16
|
||||
if (type == typeof(System.Numerics.Matrix4x4) && value is IReadOnlyList<object>)
|
||||
{
|
||||
throw new ArgumentException("Only Speckle.DoubleNumerics.Matrix4x4 is supported.", nameof(type));
|
||||
}
|
||||
#endregion
|
||||
|
||||
if (type == typeof(Matrix4x4) && value is IReadOnlyList<object> l)
|
||||
{
|
||||
double I(int index) => Convert.ToDouble(l[index]);
|
||||
convertedValue = new Matrix4x4(
|
||||
I(0),
|
||||
I(1),
|
||||
I(2),
|
||||
I(3),
|
||||
I(4),
|
||||
I(5),
|
||||
I(6),
|
||||
I(7),
|
||||
I(8),
|
||||
I(9),
|
||||
I(10),
|
||||
I(11),
|
||||
I(12),
|
||||
I(13),
|
||||
I(14),
|
||||
I(15)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the given <paramref name="type"/> is assignable from a generic type def <see cref="List{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
[Pure]
|
||||
private static bool IsGenericList(Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type typeDef = type.GetGenericTypeDefinition();
|
||||
return typeDef == typeof(List<>) || typeDef == typeof(IList<>) || typeDef == typeof(IReadOnlyList<>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class SQLiteCacheManager : ISQLiteCacheManager
|
||||
{
|
||||
private readonly string _rootPath;
|
||||
private readonly string _connectionString;
|
||||
private const string APPLICATION_NAME = "Speckle";
|
||||
private const string DATA_FOLDER = "Projects";
|
||||
|
||||
public SQLiteCacheManager(string streamId)
|
||||
{
|
||||
var basePath = SpecklePathProvider.UserApplicationDataPath();
|
||||
|
||||
try
|
||||
{
|
||||
var dir = Path.Combine(basePath, APPLICATION_NAME, DATA_FOLDER);
|
||||
_rootPath = Path.Combine(dir, $"{streamId}.db");
|
||||
|
||||
Directory.CreateDirectory(dir); //ensure dir is there
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
throw new TransportException($"Path was invalid or could not be created {_rootPath}", ex);
|
||||
}
|
||||
|
||||
_connectionString = $"Data Source={_rootPath};";
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
// NOTE: used for creating partioned object tables.
|
||||
//string[] HexChars = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
|
||||
//var cart = new List<string>();
|
||||
//foreach (var str in HexChars)
|
||||
// foreach (var str2 in HexChars)
|
||||
// cart.Add(str + str2);
|
||||
|
||||
using var c = new SqliteConnection(_connectionString);
|
||||
c.Open();
|
||||
const string COMMAND_TEXT =
|
||||
@"
|
||||
CREATE TABLE IF NOT EXISTS objects(
|
||||
hash TEXT PRIMARY KEY,
|
||||
content TEXT
|
||||
) WITHOUT ROWID;
|
||||
";
|
||||
using (var command = new SqliteCommand(COMMAND_TEXT, c))
|
||||
{
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Insert Optimisations
|
||||
|
||||
using SqliteCommand cmd0 = new("PRAGMA journal_mode='wal';", c);
|
||||
cmd0.ExecuteNonQuery();
|
||||
|
||||
//Note / Hack: This setting has the potential to corrupt the db.
|
||||
//cmd = new SqliteCommand("PRAGMA synchronous=OFF;", Connection);
|
||||
//cmd.ExecuteNonQuery();
|
||||
|
||||
using SqliteCommand cmd1 = new("PRAGMA count_changes=OFF;", c);
|
||||
cmd1.ExecuteNonQuery();
|
||||
|
||||
using SqliteCommand cmd2 = new("PRAGMA temp_store=MEMORY;", c);
|
||||
cmd2.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public IEnumerable<(string, string)> GetObjects(IEnumerable<string> ids, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using var c = new SqliteConnection(_connectionString);
|
||||
c.Open();
|
||||
using var command = new SqliteCommand("SELECT content FROM objects WHERE hash = @hash LIMIT 1 ", c);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
command.Parameters.Clear();
|
||||
command.Parameters.AddWithValue("@hash", id);
|
||||
using var reader = command.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
yield return (id, reader.GetString(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveObjects(IEnumerable<(string, string)> objects, CancellationToken cancellationToken)
|
||||
{
|
||||
using var c = new SqliteConnection(_connectionString);
|
||||
c.Open();
|
||||
using var t = c.BeginTransaction();
|
||||
const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
|
||||
|
||||
foreach (var (id, content) in objects)
|
||||
{
|
||||
using var command = new SqliteCommand(COMMAND_TEXT, c, t);
|
||||
command.Parameters.AddWithValue("@hash", id);
|
||||
command.Parameters.AddWithValue("@content", content);
|
||||
command.ExecuteNonQuery();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
t.Commit();
|
||||
}
|
||||
|
||||
public string? GetObject(string id)
|
||||
{
|
||||
using var c = new SqliteConnection(_connectionString);
|
||||
c.Open();
|
||||
using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", c);
|
||||
command.Parameters.AddWithValue("@hash", id);
|
||||
using var reader = command.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
return reader.GetString(1);
|
||||
}
|
||||
return null; // pass on the duty of null checks to consumers
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<(string, bool)> HasObjects2(IEnumerable<string> objectIds)
|
||||
{
|
||||
await Task.Delay(10).ConfigureAwait(false);
|
||||
using var c = new SqliteConnection(_connectionString);
|
||||
c.Open();
|
||||
const string COMMAND_TEXT = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1 ";
|
||||
using var command = new SqliteCommand(COMMAND_TEXT, c);
|
||||
foreach (string objectId in objectIds)
|
||||
{
|
||||
command.Parameters.Clear();
|
||||
command.Parameters.AddWithValue("@hash", objectId);
|
||||
|
||||
using var reader = command.ExecuteReader();
|
||||
bool rowFound = reader.Read();
|
||||
yield return (objectId, rowFound);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveObjectSync(string hash, string serializedObject)
|
||||
{
|
||||
using var c = new SqliteConnection(_connectionString);
|
||||
c.Open();
|
||||
const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
|
||||
|
||||
using var command = new SqliteCommand(COMMAND_TEXT, c);
|
||||
command.Parameters.AddWithValue("@hash", hash);
|
||||
command.Parameters.AddWithValue("@content", serializedObject);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class ServerObjectManager : IServerObjectManager
|
||||
{
|
||||
private static readonly char[] s_separator = { '\t' };
|
||||
|
||||
private readonly ISdkActivityFactory _activityFactory;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public ServerObjectManager(
|
||||
ISpeckleHttp speckleHttp,
|
||||
ISdkActivityFactory activityFactory,
|
||||
Uri baseUri,
|
||||
string? authorizationToken,
|
||||
int timeoutSeconds = 120
|
||||
)
|
||||
{
|
||||
_activityFactory = activityFactory;
|
||||
_client = speckleHttp.CreateHttpClient(
|
||||
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip },
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
authorizationToken: authorizationToken
|
||||
);
|
||||
_client.BaseAddress = baseUri;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
string streamId,
|
||||
IReadOnlyList<string> objectIds,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
using var _ = _activityFactory.Start();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using var childrenHttpMessage = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = new Uri($"/api/getobjects/{streamId}", UriKind.Relative),
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
|
||||
Dictionary<string, string> postParameters = new() { { "objects", JsonConvert.SerializeObject(objectIds) } };
|
||||
string serializedPayload = JsonConvert.SerializeObject(postParameters);
|
||||
childrenHttpMessage.Content = new StringContent(serializedPayload, Encoding.UTF8, "application/json");
|
||||
childrenHttpMessage.Headers.Add("Accept", "text/plain");
|
||||
|
||||
HttpResponseMessage childrenHttpResponse = await _client
|
||||
.SendAsync(childrenHttpMessage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await foreach (var (id, json) in ResponseProgress(childrenHttpResponse, progress, false, cancellationToken))
|
||||
{
|
||||
if (id is not null)
|
||||
{
|
||||
yield return (id, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> DownloadSingleObject(
|
||||
string streamId,
|
||||
string objectId,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
using var _ = _activityFactory.Start();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get root object
|
||||
using var rootHttpMessage = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = new Uri($"/objects/{streamId}/{objectId}/single", UriKind.Relative),
|
||||
Method = HttpMethod.Get,
|
||||
};
|
||||
|
||||
HttpResponseMessage rootHttpResponse = await _client
|
||||
.SendAsync(rootHttpMessage, HttpCompletionOption.ResponseContentRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var (_, json) = await ResponseProgress(rootHttpResponse, progress, true, cancellationToken)
|
||||
.FirstAsync()
|
||||
.ConfigureAwait(false);
|
||||
return json;
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<(string?, string)> ResponseProgress(
|
||||
HttpResponseMessage childrenHttpResponse,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
bool isSingle,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
childrenHttpResponse.EnsureSuccessStatusCode();
|
||||
var length = childrenHttpResponse.Content.Headers.ContentLength;
|
||||
#if NET8_0_OR_GREATER
|
||||
using Stream childrenStream = await childrenHttpResponse
|
||||
.Content.ReadAsStreamAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
#else
|
||||
using Stream childrenStream = await childrenHttpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
using var reader = new StreamReader(new ProgressStream(childrenStream, length, progress, true), Encoding.UTF8);
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
while (await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false) is { } line)
|
||||
#else
|
||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line)
|
||||
#endif
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (!isSingle)
|
||||
{
|
||||
var pcs = line.Split(s_separator, 2);
|
||||
yield return (pcs[0], pcs[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return (string.Empty, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
<PackageReference Include="GraphQL.Client"/>
|
||||
<PackageReference Include="Microsoft.CSharp" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
||||
<PackageReference Include="Polly" />
|
||||
<PackageReference Include="Polly.Contrib.WaitAndRetry" />
|
||||
<PackageReference Include="Polly.Extensions.Http" />
|
||||
@@ -44,6 +45,7 @@
|
||||
<ItemGroup Condition=" '$(Framework)' != 'NET80'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,23 +2,6 @@ using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Sdk.Transports;
|
||||
|
||||
public enum ProgressEvent
|
||||
{
|
||||
DownloadBytes,
|
||||
UploadBytes,
|
||||
DownloadObject,
|
||||
UploadObject,
|
||||
DeserializeObject,
|
||||
SerializeObject,
|
||||
}
|
||||
|
||||
public readonly record struct ProgressArgs(
|
||||
ProgressEvent ProgressEvent,
|
||||
long? Count,
|
||||
long? Total,
|
||||
long? ProcessedTotal = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Interface defining the contract for transport implementations.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Speckle.Sdk.Transports;
|
||||
|
||||
public readonly record struct ProgressArgs(ProgressEvent ProgressEvent, long Count, long? Total);
|
||||
|
||||
public enum ProgressEvent
|
||||
{
|
||||
CacheCheck,
|
||||
DownloadBytes,
|
||||
UploadBytes,
|
||||
DownloadObject,
|
||||
UploadObject,
|
||||
DeserializeObject,
|
||||
SerializeObject,
|
||||
}
|
||||
@@ -404,24 +404,20 @@ public sealed class SQLiteTransport : IDisposable, ICloneable, ITransport, IBlob
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
await _connectionLock.WaitAsync(CancellationToken).ConfigureAwait(false);
|
||||
var startTime = Stopwatch.GetTimestamp();
|
||||
try
|
||||
{
|
||||
var startTime = Stopwatch.GetTimestamp();
|
||||
using (var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", Connection))
|
||||
using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", Connection);
|
||||
command.Parameters.AddWithValue("@hash", id);
|
||||
using var reader = command.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
command.Parameters.AddWithValue("@hash", id);
|
||||
using var reader = await command.ExecuteReaderAsync(CancellationToken).ConfigureAwait(false);
|
||||
|
||||
while (await reader.ReadAsync(CancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return reader.GetString(1);
|
||||
}
|
||||
return reader.GetString(1);
|
||||
}
|
||||
|
||||
Elapsed += LoggingHelpers.GetElapsedTime(startTime, Stopwatch.GetTimestamp());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Elapsed += LoggingHelpers.GetElapsedTime(startTime, Stopwatch.GetTimestamp());
|
||||
_connectionLock.Release();
|
||||
}
|
||||
return null; // pass on the duty of null checks to consumers
|
||||
|
||||
@@ -381,9 +381,6 @@ public sealed class ServerApi : IDisposable, IServerApi
|
||||
{
|
||||
hasObjects[prop.Key] = (bool)prop.Value.NotNull();
|
||||
}
|
||||
|
||||
// Console.WriteLine($"ServerApi::HasObjects({objectIds.Count}) request in {sw.ElapsedMilliseconds / 1000.0} sec");
|
||||
|
||||
return hasObjects;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -53,6 +62,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
@@ -327,6 +342,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -361,6 +382,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
|
||||
@@ -274,10 +274,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -296,6 +298,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -330,6 +338,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.V2;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
using Speckle.Sdk.Serialization.Testing;
|
||||
|
||||
const bool skipCache = false;
|
||||
TypeLoader.Reset();
|
||||
TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly());
|
||||
|
||||
/*
|
||||
var url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small?
|
||||
var streamId = "a3ac1b2706";
|
||||
var rootId = "7d53bcf28c6696ecac8781684a0aa006";*/
|
||||
|
||||
|
||||
var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf?
|
||||
var streamId = "2099ac4b5f";
|
||||
var rootId = "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6";
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSpeckleSdk(HostApplications.Navisworks, HostAppVersion.v2023, "Test");
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
Console.WriteLine("Attach");
|
||||
Console.ReadLine();
|
||||
Console.WriteLine("Executing");
|
||||
|
||||
var progress = new Progress(true);
|
||||
var sqliteTransport = new SQLiteCacheManager(streamId);
|
||||
var serverObjects = new ServerObjectManager(
|
||||
serviceProvider.GetRequiredService<ISpeckleHttp>(),
|
||||
serviceProvider.GetRequiredService<ISdkActivityFactory>(),
|
||||
new Uri(url),
|
||||
null
|
||||
);
|
||||
var o = new ObjectLoader(sqliteTransport, serverObjects, streamId, progress);
|
||||
var process = new DeserializeProcess(progress, o);
|
||||
await process.Deserialize(rootId, default, new(skipCache)).ConfigureAwait(false);
|
||||
Console.WriteLine("Detach");
|
||||
Console.ReadLine();
|
||||
@@ -0,0 +1,36 @@
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialization.Testing;
|
||||
|
||||
public class Progress(bool write) : IProgress<ProgressArgs>
|
||||
{
|
||||
private readonly TimeSpan DEBOUNCE = TimeSpan.FromMilliseconds(500);
|
||||
private DateTime _lastTime = DateTime.UtcNow;
|
||||
|
||||
private long _totalBytes;
|
||||
|
||||
public void Report(ProgressArgs value)
|
||||
{
|
||||
if (write)
|
||||
{
|
||||
if (value.ProgressEvent == ProgressEvent.DownloadBytes)
|
||||
{
|
||||
Interlocked.Add(ref _totalBytes, value.Count);
|
||||
}
|
||||
var now = DateTime.UtcNow;
|
||||
if (now - _lastTime >= DEBOUNCE)
|
||||
{
|
||||
if (value.ProgressEvent == ProgressEvent.DownloadBytes)
|
||||
{
|
||||
Console.WriteLine(value.ProgressEvent + " t " + _totalBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(value.ProgressEvent + " c " + value.Count + " t " + value.Total);
|
||||
}
|
||||
|
||||
_lastTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Speckle.Sdk\Speckle.Sdk.csproj" />
|
||||
<ProjectReference Include="..\Speckle.Sdk.Tests.Performance\Speckle.Sdk.Tests.Performance.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,472 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.12.0, )",
|
||||
"resolved": "5.12.0",
|
||||
"contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
|
||||
}
|
||||
},
|
||||
"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.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.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"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.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"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.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.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
using Speckle.Newtonsoft.Json.Linq;
|
||||
@@ -8,6 +7,8 @@ using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
namespace Speckle.Sdk.Serialization.Tests;
|
||||
|
||||
@@ -15,6 +16,21 @@ namespace Speckle.Sdk.Serialization.Tests;
|
||||
[Description("For certain types, changing property from one type to another should be implicitly backwards compatible")]
|
||||
public class SerializationTests
|
||||
{
|
||||
private class TestLoader(string json) : IObjectLoader
|
||||
{
|
||||
public Task<(string, IReadOnlyList<string>)> GetAndCache(
|
||||
string rootId,
|
||||
CancellationToken cancellationToken,
|
||||
DeserializeOptions? options = null
|
||||
)
|
||||
{
|
||||
var childrenIds = ClosureParser.GetChildrenIds(json).ToList();
|
||||
return Task.FromResult<(string, IReadOnlyList<string>)>((json, childrenIds));
|
||||
}
|
||||
|
||||
public string? LoadId(string id) => null;
|
||||
}
|
||||
|
||||
private readonly Assembly _assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
[SetUp]
|
||||
@@ -31,10 +47,9 @@ public class SerializationTests
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> ReadAsObjects(string fullName)
|
||||
private Dictionary<string, string> ReadAsObjects(string json)
|
||||
{
|
||||
var jsonObjects = new Dictionary<string, string>();
|
||||
var json = await ReadJson(fullName);
|
||||
var array = JArray.Parse(json);
|
||||
foreach (var obj in array)
|
||||
{
|
||||
@@ -46,17 +61,53 @@ public class SerializationTests
|
||||
return jsonObjects;
|
||||
}
|
||||
|
||||
/*
|
||||
[Test]
|
||||
[TestCase("RevitObject.json")]
|
||||
public async Task RunTest2(string fileName)
|
||||
{
|
||||
var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
|
||||
var json = await ReadJson(fullName);
|
||||
var closure = await ReadAsObjects(json);
|
||||
using DeserializeProcess sut = new(null, new TestLoader(json), new TestTransport(closure));
|
||||
var @base = await sut.Deserialize("551513ff4f3596024547fc818f1f3f70");
|
||||
@base.ShouldNotBeNull();
|
||||
}*/
|
||||
|
||||
public class TestObjectLoader(Dictionary<string, string> idToObject) : IObjectLoader
|
||||
{
|
||||
public Task<(string, IReadOnlyList<string>)> GetAndCache(
|
||||
string rootId,
|
||||
CancellationToken cancellationToken,
|
||||
DeserializeOptions? options = default
|
||||
)
|
||||
{
|
||||
var json = idToObject.GetValueOrDefault(rootId);
|
||||
if (json == null)
|
||||
{
|
||||
throw new KeyNotFoundException("Root not found");
|
||||
}
|
||||
|
||||
var allChildren = ClosureParser.GetChildrenIds(json).ToList();
|
||||
return Task.FromResult<(string, IReadOnlyList<string>)>((json, allChildren));
|
||||
}
|
||||
|
||||
public string? LoadId(string id) => idToObject.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("RevitObject.json")]
|
||||
public async Task Basic_Namespace_Validation(string fileName)
|
||||
{
|
||||
var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
|
||||
var closure = await ReadAsObjects(fullName);
|
||||
var json = await ReadJson(fullName);
|
||||
var closure = ReadAsObjects(json);
|
||||
var deserializer = new SpeckleObjectDeserializer
|
||||
{
|
||||
ReadTransport = new TestTransport(closure),
|
||||
CancellationToken = default,
|
||||
};
|
||||
|
||||
foreach (var (id, objJson) in closure)
|
||||
{
|
||||
var jObject = JObject.Parse(objJson);
|
||||
@@ -77,4 +128,33 @@ public class SerializationTests
|
||||
starts.ShouldBeTrue($"{name} isn't expected");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("RevitObject.json")]
|
||||
public async Task Basic_Namespace_Validation_New(string fileName)
|
||||
{
|
||||
var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
|
||||
var json = await ReadJson(fullName);
|
||||
var closures = ReadAsObjects(json);
|
||||
var process = new DeserializeProcess(null, new TestObjectLoader(closures));
|
||||
await process.Deserialize("551513ff4f3596024547fc818f1f3f70", default);
|
||||
foreach (var (id, objJson) in closures)
|
||||
{
|
||||
var jObject = JObject.Parse(objJson);
|
||||
var oldSpeckleType = jObject["speckle_type"].NotNull().Value<string>().NotNull();
|
||||
var starts = oldSpeckleType.StartsWith("Speckle.Core.") || oldSpeckleType.StartsWith("Objects.");
|
||||
starts.ShouldBeTrue($"{oldSpeckleType} isn't expected");
|
||||
|
||||
var baseType = process.BaseCache[id];
|
||||
|
||||
starts = baseType.speckle_type.StartsWith("Speckle.Core.") || baseType.speckle_type.StartsWith("Objects.");
|
||||
starts.ShouldBeTrue($"{baseType.speckle_type} isn't expected");
|
||||
|
||||
var type = TypeLoader.GetAtomicType(baseType.speckle_type);
|
||||
type.ShouldNotBeNull();
|
||||
var name = TypeLoader.GetTypeString(type) ?? throw new ArgumentNullException();
|
||||
starts = name.StartsWith("Speckle.Core") || name.StartsWith("Objects");
|
||||
starts.ShouldBeTrue($"{name} isn't expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,10 +274,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -296,6 +298,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -330,6 +338,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
|
||||
@@ -268,10 +268,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -303,6 +305,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -346,6 +354,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
using Speckle.Sdk.Serialisation.V2;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Performance.Benchmarks;
|
||||
|
||||
@@ -13,9 +17,20 @@ namespace Speckle.Sdk.Tests.Performance.Benchmarks;
|
||||
/// How many threads on our Deserializer is optimal
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
[SimpleJob(RunStrategy.Monitoring)]
|
||||
[SimpleJob(RunStrategy.Monitoring, 0, 0, 2)]
|
||||
public class GeneralDeserializer : IDisposable
|
||||
{
|
||||
private const bool skipCache = true;
|
||||
|
||||
/*
|
||||
private const string url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small?
|
||||
private const string streamId = "a3ac1b2706";
|
||||
private const string rootId = "7d53bcf28c6696ecac8781684a0aa006";*/
|
||||
|
||||
|
||||
private const string url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf?
|
||||
private const string streamId = "2099ac4b5f";
|
||||
private const string rootId = "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6";
|
||||
private TestDataHelper _dataSource;
|
||||
|
||||
[GlobalSetup]
|
||||
@@ -24,33 +39,36 @@ public class GeneralDeserializer : IDisposable
|
||||
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly);
|
||||
_dataSource = new TestDataHelper();
|
||||
await _dataSource
|
||||
.SeedTransport(
|
||||
new Account()
|
||||
{
|
||||
serverInfo = new() { url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e" },
|
||||
},
|
||||
"2099ac4b5f",
|
||||
"30fb4cbe6eb2202b9e7b4a4fcc3dd2b6"
|
||||
)
|
||||
.SeedTransport(new Account() { serverInfo = new() { url = url } }, streamId, rootId, skipCache)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task<Base> RunTest()
|
||||
public async Task<Base> RunTest_New()
|
||||
{
|
||||
SpeckleObjectDeserializer sut = new() { ReadTransport = _dataSource.Transport };
|
||||
string data = await _dataSource.Transport.GetObject(_dataSource.ObjectId)!;
|
||||
return await sut.DeserializeAsync(data);
|
||||
var sqlite = new SQLiteCacheManager(streamId);
|
||||
var serverObjects = new ServerObjectManager(
|
||||
TestDataHelper.ServiceProvider.GetRequiredService<ISpeckleHttp>(),
|
||||
TestDataHelper.ServiceProvider.GetRequiredService<ISdkActivityFactory>(),
|
||||
new Uri(url),
|
||||
null
|
||||
);
|
||||
var o = new ObjectLoader(sqlite, serverObjects, streamId, null);
|
||||
var process = new DeserializeProcess(null, o);
|
||||
return await process.Deserialize(rootId, default, new(skipCache)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/*
|
||||
[Benchmark]
|
||||
public async Task<Base> RunTest_Old()
|
||||
{
|
||||
SpeckleObjectDeserializer sut = new() { ReadTransport = _dataSource.Transport };
|
||||
string data = await _dataSource.Transport.GetObject(_dataSource.ObjectId)!;
|
||||
return await sut.DeserializeAsync(data);
|
||||
}
|
||||
*/
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
public void Cleanup() => Dispose();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataSource.Dispose();
|
||||
}
|
||||
public void Dispose() => _dataSource.Dispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Api;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
using Speckle.Sdk.Serialisation.V2;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Performance.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// How many threads on our Deserializer is optimal
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
[SimpleJob(RunStrategy.Monitoring, 0, 0, 1)]
|
||||
public class GeneralReceiveTest : IDisposable
|
||||
{
|
||||
/*
|
||||
private const string url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small?
|
||||
private const string streamId = "a3ac1b2706";S
|
||||
private const string rootId = "7d53bcf28c6696ecac8781684a0aa006";*/
|
||||
|
||||
|
||||
private const string url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf?
|
||||
private readonly Uri _baseUrl = new("https://latest.speckle.systems");
|
||||
private const string streamId = "2099ac4b5f";
|
||||
private const string rootId = "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6";
|
||||
private TestDataHelper _dataSource;
|
||||
private IOperations _operations;
|
||||
private ITransport remoteTransport;
|
||||
|
||||
[GlobalSetup]
|
||||
public async Task Setup()
|
||||
{
|
||||
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly);
|
||||
_dataSource = new TestDataHelper();
|
||||
var acc = new Account() { serverInfo = new() { url = url } };
|
||||
await _dataSource.SeedTransport(acc, streamId, rootId, true).ConfigureAwait(false);
|
||||
_operations = TestDataHelper.ServiceProvider.GetRequiredService<IOperations>();
|
||||
// await _operations.Receive2(_baseUrl, streamId, rootId, null);
|
||||
|
||||
remoteTransport = TestDataHelper
|
||||
.ServiceProvider.GetRequiredService<IServerTransportFactory>()
|
||||
.Create(acc, streamId);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task<Base> RunTest_Receive()
|
||||
{
|
||||
return await _operations.Receive(rootId, remoteTransport, _dataSource.Transport);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task<Base> RunTest_Receive2()
|
||||
{
|
||||
return await _operations.Receive2(_baseUrl, streamId, rootId, null);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup() => Dispose();
|
||||
|
||||
public void Dispose() => _dataSource.Dispose();
|
||||
}
|
||||
@@ -32,7 +32,8 @@ public class GeneralSerializerTest
|
||||
serverInfo = new() { url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e" },
|
||||
},
|
||||
"2099ac4b5f",
|
||||
"30fb4cbe6eb2202b9e7b4a4fcc3dd2b6"
|
||||
"30fb4cbe6eb2202b9e7b4a4fcc3dd2b6",
|
||||
false
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -23,24 +23,33 @@ public sealed class TestDataHelper : IDisposable
|
||||
ServiceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public async Task SeedTransport(Account account, string streamId, string objectId)
|
||||
public async Task SeedTransport(Account account, string streamId, string objectId, bool skipCache)
|
||||
{
|
||||
// Transport = new SQLiteTransport(s_basePath, APPLICATION_NAME);
|
||||
Transport = new SQLiteTransport();
|
||||
|
||||
//seed SQLite transport with test data
|
||||
ObjectId = await SeedTransport(account, streamId, objectId, Transport).ConfigureAwait(false);
|
||||
ObjectId = await SeedTransport(account, streamId, objectId, Transport, skipCache).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<string> SeedTransport(Account account, string streamId, string objectId, ITransport transport)
|
||||
public async Task<string> SeedTransport(
|
||||
Account account,
|
||||
string streamId,
|
||||
string objectId,
|
||||
ITransport transport,
|
||||
bool skipCache
|
||||
)
|
||||
{
|
||||
using ServerTransport remoteTransport = ServiceProvider
|
||||
.GetRequiredService<IServerTransportFactory>()
|
||||
.Create(account, streamId);
|
||||
transport.BeginWrite();
|
||||
await remoteTransport.CopyObjectAndChildren(objectId, transport).ConfigureAwait(false);
|
||||
transport.EndWrite();
|
||||
await transport.WriteComplete().ConfigureAwait(false);
|
||||
if (!skipCache)
|
||||
{
|
||||
using ServerTransport remoteTransport = ServiceProvider
|
||||
.GetRequiredService<IServerTransportFactory>()
|
||||
.Create(account, streamId);
|
||||
transport.BeginWrite();
|
||||
await remoteTransport.CopyObjectAndChildren(objectId, transport).ConfigureAwait(false);
|
||||
transport.EndWrite();
|
||||
await transport.WriteComplete().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return objectId;
|
||||
}
|
||||
|
||||
@@ -98,11 +98,6 @@
|
||||
"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",
|
||||
@@ -361,10 +356,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -383,6 +380,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -417,6 +420,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
|
||||
@@ -283,10 +283,12 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[8.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.Extensions.ObjectPool": "[8.0.10, )",
|
||||
"Polly": "[7.2.3, )",
|
||||
"Polly.Contrib.WaitAndRetry": "[1.1.1, )",
|
||||
"Polly.Extensions.Http": "[3.0.0, )",
|
||||
@@ -305,6 +307,12 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
@@ -339,6 +347,12 @@
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[8.0.10, )",
|
||||
"resolved": "8.0.10",
|
||||
"contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.2.3, )",
|
||||
|
||||
Reference in New Issue
Block a user