Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better options for serializer #180

Merged
merged 7 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
using System.Text;
using Speckle.Sdk.Dependencies.Serialization;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;

namespace Speckle.Sdk.Serialization.Testing;
namespace Speckle.Sdk.Serialisation.V2;

public class DummyServerObjectManager : IServerObjectManager
public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager
{
public IEnumerable<string> GetAllObjects() => throw new NotImplementedException();

public void DeleteObject(string id) => throw new NotImplementedException();

public string? GetObject(string id) => throw new NotImplementedException();

public void SaveObject(string id, string json) => throw new NotImplementedException();

public void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException();

public bool HasObject(string objectId) => throw new NotImplementedException();
}

public class DummySendServerObjectManager : IServerObjectManager
{
public IAsyncEnumerable<(string, string)> DownloadObjects(
IReadOnlyList<string> objectIds,
Expand Down
14 changes: 5 additions & 9 deletions src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Speckle.Sdk.Serialisation.V2.Receive;

public record DeserializeOptions(
public record DeserializeProcessOptions(
bool SkipCache,
bool ThrowOnMissingReferences = true,
bool SkipInvalidConverts = false
Expand All @@ -17,10 +17,11 @@ public record DeserializeOptions(
public sealed class DeserializeProcess(
IProgress<ProgressArgs>? progress,
IObjectLoader objectLoader,
IObjectDeserializerFactory objectDeserializerFactory
IObjectDeserializerFactory objectDeserializerFactory,
DeserializeProcessOptions? options = null
) : IDeserializeProcess
{
private DeserializeOptions _options = new(false);
private readonly DeserializeProcessOptions _options = options ?? new(false);

private readonly ConcurrentDictionary<string, (string, IReadOnlyCollection<string>)> _closures = new();
private readonly ConcurrentDictionary<string, Base> _baseCache = new();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not need to optimise for cases where we have a single c# instance of Base appear multiple times in the graph

Expand All @@ -29,13 +30,8 @@ IObjectDeserializerFactory objectDeserializerFactory
public IReadOnlyDictionary<string, Base> BaseCache => _baseCache;
public long Total { get; private set; }

public async Task<Base> Deserialize(
string rootId,
CancellationToken cancellationToken,
DeserializeOptions? options = null
)
public async Task<Base> Deserialize(string rootId, CancellationToken cancellationToken)
{
_options = options ?? _options;
var (rootJson, childrenIds) = await objectLoader
.GetAndCache(rootId, _options, cancellationToken)
.ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public sealed class ObjectDeserializer(
IReadOnlyCollection<string> currentClosures,
IReadOnlyDictionary<string, Base> references,
SpeckleObjectSerializerPool pool,
DeserializeOptions? options = null
DeserializeProcessOptions? options = null
) : IObjectDeserializer
{
/// <param name="objectJson">The JSON string of the object to be deserialized <see cref="Base"/></param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public IObjectDeserializer Create(
string currentId,
IReadOnlyCollection<string> currentClosures,
IReadOnlyDictionary<string, Base> references,
DeserializeOptions? options = null
DeserializeProcessOptions? options = null
) => new ObjectDeserializer(currentId, currentClosures, references, SpeckleObjectSerializerPool.Instance, options);
}
4 changes: 2 additions & 2 deletions src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public sealed class ObjectLoader(
private int? _allChildrenCount;
private long _checkCache;
private long _cached;
private DeserializeOptions _options = new(false);
private DeserializeProcessOptions _options = new(false);

public async Task<(string, IReadOnlyCollection<string>)> GetAndCache(
string rootId,
DeserializeOptions options,
DeserializeProcessOptions options,
CancellationToken cancellationToken
)
{
Expand Down
47 changes: 47 additions & 0 deletions src/Speckle.Sdk/Serialisation/V2/Send/EmptyDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;

namespace Speckle.Sdk.Serialisation.V2.Send;

public class EmptyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => throw new NotImplementedException();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public void Add(KeyValuePair<TKey, TValue> item) { }

public void Clear() => throw new NotImplementedException();

public bool Contains(KeyValuePair<TKey, TValue> item) => false;

public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();

public bool Remove(KeyValuePair<TKey, TValue> item) => false;

public int Count => 0;
public bool IsReadOnly => false;

public void Add(TKey key, TValue value) { }

public bool ContainsKey(TKey key) => false;

public bool Remove(TKey key) => false;

public bool TryGetValue(TKey key, [UnscopedRef] out TValue value)
{
value = default!;
return false;
}

public TValue this[TKey key]
{
#pragma warning disable CA1065
get => throw new NotImplementedException();
#pragma warning restore CA1065
set { }
}

public ICollection<TKey> Keys { get; }
public ICollection<TValue> Values { get; }
}
35 changes: 29 additions & 6 deletions src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Drawing;
using System.Globalization;
using Speckle.DoubleNumerics;
Expand All @@ -21,7 +20,7 @@ public class ObjectSerializer : IObjectSerializer
{
private HashSet<object> _parentObjects = new();
private readonly Dictionary<Id, int> _currentClosures = new();
private readonly ConcurrentDictionary<Base, CacheInfo> _baseCache;
private readonly IDictionary<Base, CacheInfo> _baseCache;

private readonly bool _trackDetachedChildren;
private readonly IBasePropertyGatherer _propertyGatherer;
Expand All @@ -42,7 +41,7 @@ public class ObjectSerializer : IObjectSerializer
/// <param name="cancellationToken"></param>
public ObjectSerializer(
IBasePropertyGatherer propertyGatherer,
ConcurrentDictionary<Base, CacheInfo> baseCache,
IDictionary<Base, CacheInfo> baseCache,
bool trackDetachedChildren = false,
CancellationToken cancellationToken = default
)
Expand All @@ -61,16 +60,21 @@ public ObjectSerializer(
{
try
{
(Id, Json) item;
try
{
var item = SerializeBase(baseObj, true).NotNull();
_baseCache.TryAdd(baseObj, new(item.Item2, _currentClosures.Freeze()));
return [new(item.Item1, item.Item2), .. _chunks];
item = SerializeBase(baseObj, true).NotNull();
}
catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException)
{
throw new SpeckleSerializeException($"Failed to extract (pre-serialize) properties from the {baseObj}", ex);
}
_baseCache[baseObj] = new(item.Item2, _currentClosures);
yield return (item.Item1, item.Item2);
foreach (var chunk in _chunks)
{
yield return chunk;
}
}
finally
{
Expand All @@ -90,6 +94,25 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute
return;
}

switch (obj)
{
case double d:
writer.WriteValue(d);
return;
case string d:
writer.WriteValue(d);
return;
case bool d:
writer.WriteValue(d);
return;
case int d:
writer.WriteValue(d);
return;
case long d:
writer.WriteValue(d);
return;
}

if (obj.GetType().IsPrimitive || obj is string)
{
writer.WriteValue(obj);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Models;

Expand All @@ -7,8 +6,6 @@ namespace Speckle.Sdk.Serialisation.V2.Send;
[GenerateAutoInterface]
public class ObjectSerializerFactory(IBasePropertyGatherer propertyGatherer) : IObjectSerializerFactory
{
public IObjectSerializer Create(
ConcurrentDictionary<Base, CacheInfo> baseCache,
CancellationToken cancellationToken
) => new ObjectSerializer(propertyGatherer, baseCache, true, cancellationToken);
public IObjectSerializer Create(IDictionary<Base, CacheInfo> baseCache, CancellationToken cancellationToken) =>
new ObjectSerializer(propertyGatherer, baseCache, true, cancellationToken);
}
19 changes: 8 additions & 11 deletions src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Speckle.Sdk.Serialisation.V2.Send;

public record SerializeProcessOptions(bool SkipCacheRead, bool SkipCacheWrite, bool SkipServer);
public record SerializeProcessOptions(bool SkipCacheRead, bool SkipCacheWrite, bool SkipServer, bool CacheBases);

public readonly record struct SerializeProcessResults(
string RootId,
Expand All @@ -22,11 +22,15 @@ public class SerializeProcess(
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
IServerObjectManager serverObjectManager,
IBaseChildFinder baseChildFinder,
IObjectSerializerFactory objectSerializerFactory
IObjectSerializerFactory objectSerializerFactory,
SerializeProcessOptions? options = null
) : ChannelSaver, ISerializeProcess
{
private readonly SerializeProcessOptions _options = options ?? new(false, false, false, true);
private readonly ConcurrentDictionary<Id, Json> _jsonCache = new();
private readonly ConcurrentDictionary<Base, CacheInfo> _baseCache = new();

private readonly IDictionary<Base, CacheInfo> _baseCache =
options?.CacheBases ?? true ? new ConcurrentDictionary<Base, CacheInfo>() : new EmptyDictionary<Base, CacheInfo>();
private readonly ConcurrentDictionary<Id, ObjectReference> _objectReferences = new();

private long _totalFound;
Expand All @@ -35,15 +39,8 @@ IObjectSerializerFactory objectSerializerFactory
private long _cached;
private long _serialized;

private SerializeProcessOptions _options = new(false, false, false);

public async Task<SerializeProcessResults> Serialize(
Base root,
CancellationToken cancellationToken,
SerializeProcessOptions? options = null
)
public async Task<SerializeProcessResults> Serialize(Base root, CancellationToken cancellationToken)
{
_options = options ?? _options;
var channelTask = Start(cancellationToken);
await Traverse(root, true, cancellationToken).ConfigureAwait(false);
await channelTask.ConfigureAwait(false);
Expand Down
39 changes: 33 additions & 6 deletions src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ ISerializeProcess CreateSerializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress
IProgress<ProgressArgs>? progress,
SerializeProcessOptions? options = null
);
IDeserializeProcess CreateDeserializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress
IProgress<ProgressArgs>? progress,
DeserializeProcessOptions? options = null
);

public ISerializeProcess CreateSerializeProcess(
SerializeProcessOptions? options = null,
IProgress<ProgressArgs>? progress = null
);
}

Expand All @@ -36,7 +43,8 @@ public ISerializeProcess CreateSerializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress
IProgress<ProgressArgs>? progress,
SerializeProcessOptions? options = null
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
Expand All @@ -46,21 +54,40 @@ public ISerializeProcess CreateSerializeProcess(
sqLiteJsonCacheManager,
serverObjectManager,
baseChildFinder,
objectSerializerFactory
objectSerializerFactory,
options
);
}

public ISerializeProcess CreateSerializeProcess(
SerializeProcessOptions? options = null,
IProgress<ProgressArgs>? progress = null
)
{
var sqLiteJsonCacheManager = new DummySqLiteJsonCacheManager();
var serverObjectManager = new DummySendServerObjectManager();
return new SerializeProcess(
progress,
sqLiteJsonCacheManager,
serverObjectManager,
baseChildFinder,
objectSerializerFactory,
options
);
}

public IDeserializeProcess CreateDeserializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress
IProgress<ProgressArgs>? progress,
DeserializeProcessOptions? options = null
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken);

var objectLoader = new ObjectLoader(sqLiteJsonCacheManager, serverObjectManager, progress);
return new DeserializeProcess(progress, objectLoader, objectDeserializerFactory);
return new DeserializeProcess(progress, objectLoader, objectDeserializerFactory, options);
}
}
16 changes: 10 additions & 6 deletions tests/Speckle.Sdk.Serialization.Testing/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,20 @@
new ObjectDeserializerFactory(),
serviceProvider.GetRequiredService<ISqLiteJsonCacheManagerFactory>()
);
var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress);
var @base = await process.Deserialize(rootId, default, new(skipCacheReceive)).ConfigureAwait(false);
var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress, new(skipCacheReceive));
var @base = await process.Deserialize(rootId, default).ConfigureAwait(false);
Console.WriteLine("Deserialized");
Console.ReadLine();
Console.WriteLine("Executing");

var process2 = factory.CreateSerializeProcess(new Uri(url), streamId, token, progress);
await process2
.Serialize(@base, default, new SerializeProcessOptions(skipCacheSendCheck, skipCacheSendSave, true))
.ConfigureAwait(false);
var process2 = factory.CreateSerializeProcess(
new Uri(url),
streamId,
token,
progress,
new SerializeProcessOptions(skipCacheSendCheck, skipCacheSendSave, true, true)
);
await process2.Serialize(@base, default).ConfigureAwait(false);
Console.WriteLine("Detach");
Console.ReadLine();
#pragma warning restore CA1506
Loading