From 4cc78c4bc911d0c671c16f57cc15076e96fa1b6c Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 5 Nov 2024 09:56:54 +0000 Subject: [PATCH] Serialize using a Channel (#146) * 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 * Hides ObjectPool dependency * fmt * Use the 1.0 BCL async to try to be more compatible * rename to dependencies * Move Polly to internal dependencies * format * remove more old references * remove stackchannel * fixes for registration * remove console writeline * add cache check shortcut for root object * start refactoring send * recevie2 benchmark * add test for deserialize new * use channels for sending * test and fixes * Use same asyncinterfaces as Dynamo. Merge fixes * clean up * fix download object progress * put back from bad merge * intermediate commit: separating get child function from serializer * send didn't error * add channels * Use net48, netstandard2.1 and net8 * remove collection special case * have to make a tree of tasks even though it may serialize things twice * pre-id changing during serialize * need AsyncInterfaces for net48 :( * options changes * revert to netstandard2.0 and net8.0 * fix totals * revert httpcontext changes * format * clean up * active tasks works when accounting for id not being stable * add id tests * more fixes * works * format * Convert to BaseItem and use single SQLite checks to avoid locks * use locks and batch sqlite operations * hook up and handle null ids * remove unused parameter * remove progress from serializer itself * invert has objects call * readd object references * format * fix tests * remove active tasks check * bug fix for json cache * remove locks from sqlite * General Send test * add childclosures * redo extract all to be enumerable * group tests in projects * caching json does matter * cache checking should be managed by channels * format * Merge pull request #152 from specklesystems/new-json-test Uses a new objects test in Revit for serialization tests * add skip * add new roundtrip test * fix finish * clean up tests * check happens in serialize...don't do it twice * better progress reporting * fix progress reporting * only use detached properties when children gathering * move detached tests * add detached tests * fix merge * Fix progress change * fix more tests --------- Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Co-authored-by: Claire Kuang --- .../Serialization/ChannelLoader.cs | 26 +- .../Serialization/ChannelSaver.cs | 46 +++ .../Api/Operations/Operations.Receive.cs | 2 +- .../Api/Operations/Operations.Send.cs | 40 ++ src/Speckle.Sdk/Api/Operations/Operations.cs | 5 +- src/Speckle.Sdk/Credentials/AccountManager.cs | 2 +- .../Credentials/AuthFlowException.cs | 2 + src/Speckle.Sdk/Helpers/Http.cs | 2 +- src/Speckle.Sdk/Serialisation/IdGenerator.cs | 19 + .../SpeckleObjectDeserializer.cs | 2 +- .../Serialisation/SpeckleObjectSerializer.cs | 14 +- .../V2/Receive/DeserializeProcess.cs | 3 +- .../Serialisation/V2/Receive/ObjectLoader.cs | 33 +- .../Serialisation/V2/SQLiteCacheManager.cs | 58 +-- .../V2/SQLiteReceiveCacheManager.cs | 49 +++ .../V2/SQLiteSendCacheManager.cs | 57 +++ .../V2/Send/PropertyAttributeInfo.cs | 24 ++ .../Serialisation/V2/Send/SerializeProcess.cs | 156 ++++++++ .../V2/Send/SpeckleBaseChildFinder.cs | 41 ++ .../V2/Send/SpeckleBasePropertyGatherer.cs | 99 +++++ .../V2/Send/SpeckleObjectSerializer.cs | 361 ++++++++++++++++++ .../Serialisation/V2/ServerObjectManager.cs | 79 ++++ src/Speckle.Sdk/SpeckleException.cs | 2 + src/Speckle.Sdk/Transports/DiskTransport.cs | 1 - src/Speckle.Sdk/Transports/MemoryTransport.cs | 1 - src/Speckle.Sdk/Transports/ProgressArgs.cs | 14 +- src/Speckle.Sdk/Transports/SQLiteTransport.cs | 2 - .../Transports/TransportHelpers.cs | 3 - .../DummyServerObjectManager.cs | 47 +++ .../Program.cs | 27 +- .../Progress.cs | 2 +- .../BaseComparer.cs | 40 ++ .../DetachedTests.cs | 250 ++++++++++++ .../DummyReceiveServerObjectManager.cs | 59 +++ .../DummySendServerObjectManager.cs | 58 +++ .../DummySqLiteReceiveManager.cs | 13 + .../DummySqLiteSendManager.cs | 13 + .../SerializationTests.cs | 96 +++++ .../Benchmarks/GeneralDeserializerTest.cs | 2 +- .../Benchmarks/GeneralSendTest.cs | 84 ++++ .../Speckle.Sdk.Tests.Performance/Program.cs | 4 + .../Api/Operations/SendReceiveLocal.cs | 12 +- .../Models/BaseTests.cs | 9 + .../Transports/TransportTests.cs | 13 - 44 files changed, 1731 insertions(+), 141 deletions(-) create mode 100644 src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs create mode 100644 src/Speckle.Sdk/Serialisation/IdGenerator.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/SQLiteReceiveCacheManager.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/SQLiteSendCacheManager.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/PropertyAttributeInfo.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBaseChildFinder.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBasePropertyGatherer.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/SpeckleObjectSerializer.cs create mode 100644 tests/Speckle.Sdk.Serialization.Testing/DummyServerObjectManager.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs create mode 100644 tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs index d7bc96df..7d2ab39b 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs @@ -1,33 +1,29 @@ using Open.ChannelExtensions; -namespace Speckle.Sdk.Serialisation.V2.Receive; +namespace Speckle.Sdk.Dependencies.Serialization; public abstract class ChannelLoader { - private const int HTTP_ID_CHUNK_SIZE = 500; + private const int HTTP_GET_CHUNK_SIZE = 500; private const int MAX_PARALLELISM_HTTP = 4; + private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2); + private static readonly int MAX_CACHE_PARALLELISM = Environment.ProcessorCount; protected async Task GetAndCache(IEnumerable allChildrenIds, CancellationToken cancellationToken = default) => await allChildrenIds .ToChannel(cancellationToken: cancellationToken) - .Pipe(Environment.ProcessorCount, CheckCache, cancellationToken: cancellationToken) + .Pipe(MAX_CACHE_PARALLELISM, CheckCache, cancellationToken: cancellationToken) .Filter(x => x is not null) - .Batch(HTTP_ID_CHUNK_SIZE) - .WithTimeout(TimeSpan.FromSeconds(2)) - .PipeAsync( - MAX_PARALLELISM_HTTP, - async x => await DownloadAndCache(x).ConfigureAwait(false), - -1, - false, - cancellationToken - ) + .Batch(HTTP_GET_CHUNK_SIZE) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .PipeAsync(MAX_PARALLELISM_HTTP, async x => await Download(x).ConfigureAwait(false), -1, false, cancellationToken) .Join() - .ReadAllConcurrently(Environment.ProcessorCount, SaveToCache, cancellationToken) + .ReadAllConcurrently(MAX_CACHE_PARALLELISM, SaveToCache, cancellationToken) .ConfigureAwait(false); public abstract string? CheckCache(string id); - public abstract Task> DownloadAndCache(List ids); + public abstract Task> Download(List ids); - public abstract void SaveToCache((string, string) x); + public abstract void SaveToCache(BaseItem x); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs new file mode 100644 index 00000000..1ae73993 --- /dev/null +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs @@ -0,0 +1,46 @@ +using System.Threading.Channels; +using Open.ChannelExtensions; + +namespace Speckle.Sdk.Dependencies.Serialization; + +public readonly record struct BaseItem(string Id, string Json, bool NeedsStorage); + +public abstract class ChannelSaver +{ + private const int HTTP_SEND_CHUNK_SIZE = 500; + private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2); + private const int MAX_PARALLELISM_HTTP = 4; + private const int MAX_CACHE_WRITE_PARALLELISM = 1; + private const int MAX_CACHE_BATCH = 100; + + private readonly Channel _checkCacheChannel = Channel.CreateUnbounded(); + + public Task Start(string streamId, CancellationToken cancellationToken = default) => + _checkCacheChannel + .Reader.Batch(HTTP_SEND_CHUNK_SIZE) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .PipeAsync( + MAX_PARALLELISM_HTTP, + async x => await SendToServer(streamId, x, cancellationToken).ConfigureAwait(false), + -1, + false, + cancellationToken + ) + .Join() + .Batch(MAX_CACHE_BATCH) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken); + + public async Task Save(BaseItem item, CancellationToken cancellationToken = default) => + await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false); + + public void Done() => _checkCacheChannel.Writer.TryComplete(); + + public abstract Task> SendToServer( + string streamId, + List batch, + CancellationToken cancellationToken + ); + + public abstract void SaveToCache(List item); +} diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs index 60d0c296..5c516798 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs @@ -26,7 +26,7 @@ public partial class Operations try { - var sqliteTransport = new SQLiteCacheManager(streamId); + var sqliteTransport = new SQLiteReceiveCacheManager(streamId); var serverObjects = new ServerObjectManager(speckleHttp, activityFactory, url, authorizationToken); var o = new ObjectLoader(sqliteTransport, serverObjects, streamId, onProgressAction); var process = new DeserializeProcess(onProgressAction, o); diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs index 902625a3..ff425a33 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs @@ -4,12 +4,52 @@ using Speckle.Newtonsoft.Json.Linq; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; +using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; namespace Speckle.Sdk.Api; public partial class Operations { + public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send2( + Uri url, + string streamId, + string? authorizationToken, + Base value, + IProgress? onProgressAction = null, + CancellationToken cancellationToken = default + ) + { + using var receiveActivity = activityFactory.Start("Operations.Send"); + metricsFactory.CreateCounter("Send").Add(1); + + try + { + var sqliteTransport = new SQLiteSendCacheManager(streamId); + var serverObjects = new ServerObjectManager(speckleHttp, activityFactory, url, authorizationToken); + var process = new SerializeProcess( + onProgressAction, + sqliteTransport, + serverObjects, + speckleBaseChildFinder, + speckleBasePropertyGatherer + ); + var (rootObjId, convertedReferences) = await process + .Serialize(streamId, value, cancellationToken) + .ConfigureAwait(false); + + receiveActivity?.SetStatus(SdkActivityStatusCode.Ok); + return new(rootObjId, convertedReferences); + } + catch (Exception ex) + { + receiveActivity?.SetStatus(SdkActivityStatusCode.Error); + receiveActivity?.RecordException(ex); + throw; + } + } + /// /// Sends a Speckle Object to the provided and (optionally) the default local cache /// diff --git a/src/Speckle.Sdk/Api/Operations/Operations.cs b/src/Speckle.Sdk/Api/Operations/Operations.cs index b3bf342c..ed41111e 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Speckle.InterfaceGenerator; using Speckle.Sdk.Helpers; using Speckle.Sdk.Logging; +using Speckle.Sdk.Serialisation.V2.Send; namespace Speckle.Sdk.Api; @@ -15,5 +16,7 @@ public partial class Operations( ILogger logger, ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory, - ISdkMetricsFactory metricsFactory + ISdkMetricsFactory metricsFactory, + ISpeckleBaseChildFinder speckleBaseChildFinder, + ISpeckleBasePropertyGatherer speckleBasePropertyGatherer ) : IOperations; diff --git a/src/Speckle.Sdk/Credentials/AccountManager.cs b/src/Speckle.Sdk/Credentials/AccountManager.cs index 70d55827..c824a932 100644 --- a/src/Speckle.Sdk/Credentials/AccountManager.cs +++ b/src/Speckle.Sdk/Credentials/AccountManager.cs @@ -807,7 +807,7 @@ public class AccountManager(ISpeckleApplication application, ILoggerServer endpoint to get header /// if response contains FE2 header and the value was /// response contained FE2 header, but the value was , empty, or not parseable to a - /// Request to failed to send or response was not successful + /// Request to failed to send or response was not successful private async Task IsFrontend2Server(Uri server) { using var httpClient = speckleHttp.CreateHttpClient(); diff --git a/src/Speckle.Sdk/Credentials/AuthFlowException.cs b/src/Speckle.Sdk/Credentials/AuthFlowException.cs index 65d32a2e..cb720d00 100644 --- a/src/Speckle.Sdk/Credentials/AuthFlowException.cs +++ b/src/Speckle.Sdk/Credentials/AuthFlowException.cs @@ -1,6 +1,8 @@ namespace Speckle.Sdk.Credentials; +#pragma warning disable CA2237 public sealed class AuthFlowException : Exception +#pragma warning restore CA2237 { public AuthFlowException(string? message, Exception? innerException) : base(message, innerException) { } diff --git a/src/Speckle.Sdk/Helpers/Http.cs b/src/Speckle.Sdk/Helpers/Http.cs index e7169c0b..a468abd0 100644 --- a/src/Speckle.Sdk/Helpers/Http.cs +++ b/src/Speckle.Sdk/Helpers/Http.cs @@ -13,7 +13,7 @@ public class SpeckleHttp(ILogger logger, ISpeckleHttpClientHandlerF /// Sends a GET request to the provided /// /// The URI that should be pinged - /// Request to failed + /// Request to failed public async Task HttpPing(Uri uri) { try diff --git a/src/Speckle.Sdk/Serialisation/IdGenerator.cs b/src/Speckle.Sdk/Serialisation/IdGenerator.cs new file mode 100644 index 00000000..b06fa9ac --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/IdGenerator.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.Contracts; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Models; + +namespace Speckle.Sdk.Serialisation; + +public static class IdGenerator +{ + [Pure] + public static string ComputeId(string serialized) + { +#if NET6_0_OR_GREATER + string hash = Crypt.Sha256(serialized.AsSpan(), length: HashUtility.HASH_LENGTH); +#else + string hash = Crypt.Sha256(serialized, length: HashUtility.HASH_LENGTH); +#endif + return hash; + } +} diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs index 8aee1d97..4ab7eaa9 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs @@ -135,7 +135,7 @@ public sealed class SpeckleObjectDeserializer var closures = ClosureParser.GetClosures(reader); if (closures.Any()) { - _total = closures.Select(x => x.Item1).Concat(_deserializedObjects.Keys).Distinct().Count(); + _total = 0; foreach (var closure in closures) { string objId = closure.Item1; diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs index 8239450c..698bde0d 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Diagnostics; -using System.Diagnostics.Contracts; using System.Drawing; using System.Globalization; using System.Reflection; @@ -359,7 +358,7 @@ public class SpeckleObjectSerializer if (writer is SerializerIdWriter serializerIdWriter) { (var json, writer) = serializerIdWriter.FinishIdWriter(); - id = ComputeId(json); + id = IdGenerator.ComputeId(json); } else { @@ -434,17 +433,6 @@ public class SpeckleObjectSerializer } } - [Pure] - private static string ComputeId(string serialized) - { -#if NET6_0_OR_GREATER - string hash = Crypt.Sha256(serialized.AsSpan(), length: HashUtility.HASH_LENGTH); -#else - string hash = Crypt.Sha256(serialized, length: HashUtility.HASH_LENGTH); -#endif - return hash; - } - private void StoreObject(string objectId, string objectJson) { _stopwatch.Stop(); diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs index 322cc92e..f1f5283b 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Data; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation.Utilities; using Speckle.Sdk.Transports; @@ -89,7 +90,7 @@ public sealed class DeserializeProcess(IProgress? progress, IObjec var json = objectLoader.LoadId(id); if (json == null) { - throw new InvalidOperationException(); + throw new MissingPrimaryKeyException($"Missing object id in SQLite cache: {id}"); } var childrenIds = ClosureParser.GetClosures(json).OrderByDescending(x => x.Item2).Select(x => x.Item1).ToList(); closures = (json, childrenIds); diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs index e890f977..782ba9ae 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs @@ -1,5 +1,6 @@ using Speckle.InterfaceGenerator; using Speckle.Sdk.Common; +using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Serialisation.Utilities; using Speckle.Sdk.Transports; @@ -7,7 +8,7 @@ namespace Speckle.Sdk.Serialisation.V2.Receive; [GenerateAutoInterface] public sealed class ObjectLoader( - ISQLiteCacheManager sqLiteCacheManager, + ISQLiteReceiveCacheManager sqliteReceiveCacheManager, IServerObjectManager serverObjectManager, string streamId, IProgress? progress @@ -28,7 +29,7 @@ public sealed class ObjectLoader( string? rootJson; if (!options.SkipCache) { - rootJson = sqLiteCacheManager.GetObject(rootId); + rootJson = sqliteReceiveCacheManager.GetObject(rootId); if (rootJson != null) { //assume everything exists as the root is there. @@ -50,7 +51,10 @@ public sealed class ObjectLoader( await GetAndCache(allChildrenIds, cancellationToken).ConfigureAwait(false); //save the root last to shortcut later - sqLiteCacheManager.SaveObjectSync(rootId, rootJson); + if (!options.SkipCache) + { + sqliteReceiveCacheManager.SaveObject(new(rootId, rootJson, true)); + } return (rootJson, allChildrenIds); } @@ -59,7 +63,7 @@ public sealed class ObjectLoader( { _checkCache++; progress?.Report(new(ProgressEvent.CacheCheck, _checkCache, _allChildrenCount)); - if (!_options.SkipCache && !sqLiteCacheManager.HasObject(id)) + if (!_options.SkipCache && !sqliteReceiveCacheManager.HasObject(id)) { return id; } @@ -68,11 +72,9 @@ public sealed class ObjectLoader( } [AutoInterfaceIgnore] - public override async Task> DownloadAndCache(List ids) + public override async Task> Download(List ids) { - var count = 0L; - progress?.Report(new(ProgressEvent.DownloadObject, count, _allChildrenCount)); - var toCache = new List<(string, string)>(); + var toCache = new List(); await foreach ( var (id, json) in serverObjectManager.DownloadObjects( streamId, @@ -82,25 +84,22 @@ public sealed class ObjectLoader( ) ) { - count++; - progress?.Report(new(ProgressEvent.DownloadObject, count, _allChildrenCount)); - toCache.Add((id, json)); + toCache.Add(new(id, json, true)); } return toCache; } [AutoInterfaceIgnore] - public override void SaveToCache((string, string) x) + public override void SaveToCache(BaseItem x) { if (!_options.SkipCache) { - sqLiteCacheManager.SaveObjectSync(x.Item1, x.Item2); + sqliteReceiveCacheManager.SaveObject(x); + _cached++; + progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _allChildrenCount)); } - - _cached++; - progress?.Report(new(ProgressEvent.Cached, _cached, _allChildrenCount)); } - public string? LoadId(string id) => sqLiteCacheManager.GetObject(id); + public string? LoadId(string id) => sqliteReceiveCacheManager.GetObject(id); } diff --git a/src/Speckle.Sdk/Serialisation/V2/SQLiteCacheManager.cs b/src/Speckle.Sdk/Serialisation/V2/SQLiteCacheManager.cs index 42309985..801721c0 100644 --- a/src/Speckle.Sdk/Serialisation/V2/SQLiteCacheManager.cs +++ b/src/Speckle.Sdk/Serialisation/V2/SQLiteCacheManager.cs @@ -1,19 +1,16 @@ -using Microsoft.Data.Sqlite; -using Speckle.InterfaceGenerator; +using Microsoft.Data.Sqlite; using Speckle.Sdk.Logging; using Speckle.Sdk.Transports; namespace Speckle.Sdk.Serialisation.V2; -[GenerateAutoInterface] -public class SQLiteCacheManager : ISQLiteCacheManager +public abstract class SQLiteCacheManager { private readonly string _rootPath; - private readonly string _connectionString; private const string APPLICATION_NAME = "Speckle"; private const string DATA_FOLDER = "Projects"; - public SQLiteCacheManager(string streamId) + protected SQLiteCacheManager(string streamId) { var basePath = SpecklePathProvider.UserApplicationDataPath(); @@ -30,7 +27,7 @@ public class SQLiteCacheManager : ISQLiteCacheManager throw new TransportException($"Path was invalid or could not be created {_rootPath}", ex); } - _connectionString = $"Data Source={_rootPath};"; + ConnectionString = $"Data Source={_rootPath};"; Initialize(); } @@ -43,7 +40,7 @@ public class SQLiteCacheManager : ISQLiteCacheManager // foreach (var str2 in HexChars) // cart.Add(str + str2); - using var c = new SqliteConnection(_connectionString); + using var c = new SqliteConnection(ConnectionString); c.Open(); const string COMMAND_TEXT = @" @@ -71,44 +68,13 @@ public class SQLiteCacheManager : ISQLiteCacheManager using SqliteCommand cmd2 = new("PRAGMA temp_store=MEMORY;", c); cmd2.ExecuteNonQuery(); + + using SqliteCommand cmd3 = new("PRAGMA mmap_size = 30000000000;", c); + cmd3.ExecuteNonQuery(); + + using SqliteCommand cmd4 = new("PRAGMA page_size = 32768;", c); + cmd4.ExecuteNonQuery(); } - 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 bool HasObject(string objectId) - { - 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); - command.Parameters.AddWithValue("@hash", objectId); - - using var reader = command.ExecuteReader(); - bool rowFound = reader.Read(); - return 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(); - } + protected string ConnectionString { get; } } diff --git a/src/Speckle.Sdk/Serialisation/V2/SQLiteReceiveCacheManager.cs b/src/Speckle.Sdk/Serialisation/V2/SQLiteReceiveCacheManager.cs new file mode 100644 index 00000000..4f380f99 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/SQLiteReceiveCacheManager.cs @@ -0,0 +1,49 @@ +using Microsoft.Data.Sqlite; +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Dependencies.Serialization; + +namespace Speckle.Sdk.Serialisation.V2; + +[GenerateAutoInterface] +public class SQLiteReceiveCacheManager(string streamId) : SQLiteCacheManager(streamId), ISQLiteReceiveCacheManager +{ + 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 void SaveObject(BaseItem item) + { + 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", item.Id); + command.Parameters.AddWithValue("@content", item.Json); + command.ExecuteNonQuery(); + } + + public bool HasObject(string objectId) + { + 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); + command.Parameters.AddWithValue("@hash", objectId); + + using var reader = command.ExecuteReader(); + bool rowFound = reader.Read(); + return rowFound; + } +} diff --git a/src/Speckle.Sdk/Serialisation/V2/SQLiteSendCacheManager.cs b/src/Speckle.Sdk/Serialisation/V2/SQLiteSendCacheManager.cs new file mode 100644 index 00000000..609b3b86 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/SQLiteSendCacheManager.cs @@ -0,0 +1,57 @@ +using Microsoft.Data.Sqlite; +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Dependencies.Serialization; + +namespace Speckle.Sdk.Serialisation.V2; + +[GenerateAutoInterface] +public class SQLiteSendCacheManager(string streamId) : SQLiteCacheManager(streamId), ISQLiteSendCacheManager +{ + 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 bool HasObject(string objectId) + { + 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); + command.Parameters.AddWithValue("@hash", objectId); + + using var reader = command.ExecuteReader(); + bool rowFound = reader.Read(); + return rowFound; + } + + public void SaveObjects(List items) + { + 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)"; + + using var command = new SqliteCommand(COMMAND_TEXT, c); + command.Transaction = t; + var idParam = command.Parameters.Add("@hash", SqliteType.Text); + var jsonParam = command.Parameters.Add("@content", SqliteType.Text); + foreach (var item in items) + { + idParam.Value = item.Id; + jsonParam.Value = item.Json; + command.ExecuteNonQuery(); + } + t.Commit(); + } +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/PropertyAttributeInfo.cs b/src/Speckle.Sdk/Serialisation/V2/Send/PropertyAttributeInfo.cs new file mode 100644 index 00000000..efa4d9a9 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/PropertyAttributeInfo.cs @@ -0,0 +1,24 @@ +using Speckle.Newtonsoft.Json; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +public readonly struct PropertyAttributeInfo +{ + public PropertyAttributeInfo( + bool isDetachable, + bool isChunkable, + int chunkSize, + JsonPropertyAttribute? jsonPropertyAttribute + ) + { + IsDetachable = isDetachable || isChunkable; + IsChunkable = isChunkable; + ChunkSize = chunkSize; + JsonPropertyInfo = jsonPropertyAttribute; + } + + public readonly bool IsDetachable; + public readonly bool IsChunkable; + public readonly int ChunkSize; + public readonly JsonPropertyAttribute? JsonPropertyInfo; +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs new file mode 100644 index 00000000..dcb7e74b --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs @@ -0,0 +1,156 @@ +using System.Collections.Concurrent; +using Speckle.Sdk.Common; +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Models; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +public record SerializeProcessOptions(bool SkipCache, bool SkipServer); + +public class SerializeProcess( + IProgress? progress, + ISQLiteSendCacheManager sqliteSendCacheManager, + IServerObjectManager serverObjectManager, + ISpeckleBaseChildFinder speckleBaseChildFinder, + ISpeckleBasePropertyGatherer speckleBasePropertyGatherer +) : ChannelSaver +{ + private readonly ConcurrentDictionary _jsonCache = new(); + private readonly ConcurrentDictionary _objectReferences = new(); + + private long _total; + private long _cached; + private long _serialized; + + private SerializeProcessOptions _options = new(false, false); + + public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Serialize( + string streamId, + Base root, + CancellationToken cancellationToken, + SerializeProcessOptions? options = null + ) + { + _options = options ?? _options; + var channelTask = Start(streamId, cancellationToken); + await Traverse(root, true, cancellationToken).ConfigureAwait(false); + await channelTask.ConfigureAwait(false); + return (root.id, _objectReferences); + } + + private async Task>> Traverse(Base obj, bool isEnd, CancellationToken cancellationToken) + { + var tasks = new List>>>(); + foreach (var child in speckleBaseChildFinder.GetChildren(obj)) + { + Interlocked.Increment(ref _total); + // tmp is necessary because of the way closures close over loop variables + var tmp = child; + var t = Task + .Factory.StartNew( + () => Traverse(tmp, false, cancellationToken), + cancellationToken, + TaskCreationOptions.AttachedToParent, + TaskScheduler.Default + ) + .Unwrap(); + tasks.Add(t); + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + var closures = tasks + .Select(t => t.Result) + .Aggregate( + new List>(), + (a, s) => + { + a.AddRange(s); + return a; + } + ) + .ToList(); + + var item = Serialise(obj, closures); + Interlocked.Increment(ref _serialized); + progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _serialized, _total)); + if (item?.NeedsStorage ?? false) + { + await Save(item.Value, cancellationToken).ConfigureAwait(false); + } + if (isEnd) + { + Done(); + } + return closures; + } + + //leave this sync + private BaseItem? Serialise(Base obj, List> childClosures) + { + if (obj.id != null && _jsonCache.ContainsKey(obj.id)) + { + return null; + } + + string? json = null; + if (!_options.SkipCache && obj.id != null) + { + json = sqliteSendCacheManager.GetObject(obj.id); + } + if (json == null) + { + var id = obj.id; + if (id is null || !_jsonCache.TryGetValue(id, out json)) + { + SpeckleObjectSerializer2 serializer2 = new(speckleBasePropertyGatherer, childClosures); + json = serializer2.Serialize(obj); + obj.id.NotNull(); + foreach (var kvp in serializer2.ObjectReferences) + { + _objectReferences.TryAdd(kvp.Key, kvp.Value); + } + + _jsonCache.TryAdd(obj.id, json); + if (id is not null && id != obj.id) + { + //in case the ids changes which is due to id hash algorithm changing + _jsonCache.TryAdd(id, json); + } + } + return new BaseItem(obj.id.NotNull(), json, true); + } + return new BaseItem(obj.id.NotNull(), json.NotNull(), false); + } + + public override async Task> SendToServer( + string streamId, + List batch, + CancellationToken cancellationToken + ) + { + if (batch.Count == 0) + { + return batch; + } + + if (!_options.SkipServer) + { + await serverObjectManager.UploadObjects(streamId, batch, true, progress, cancellationToken).ConfigureAwait(false); + } + return batch; + } + + public override void SaveToCache(List items) + { + if (!_options.SkipCache) + { + sqliteSendCacheManager.SaveObjects(items); + Interlocked.Exchange(ref _cached, _cached + items.Count); + progress?.Report(new(ProgressEvent.CachedToLocal, _cached, null)); + } + } +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBaseChildFinder.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBaseChildFinder.cs new file mode 100644 index 00000000..140a51ec --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBaseChildFinder.cs @@ -0,0 +1,41 @@ +using System.Collections; +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Models; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +[GenerateAutoInterface] +public class SpeckleBaseChildFinder(ISpeckleBasePropertyGatherer propertyGatherer) : ISpeckleBaseChildFinder +{ + public IEnumerable GetChildren(Base obj) + { + var props = propertyGatherer.ExtractAllProperties(obj); + foreach (var kvp in props.Where(x => x.PropertyAttributeInfo.IsDetachable)) + { + if (kvp.Value is Base child) + { + yield return child; + } + if (kvp.Value is ICollection c) + { + foreach (var childC in c) + { + if (childC is Base b) + { + yield return b; + } + } + } + if (kvp.Value is IDictionary d) + { + foreach (DictionaryEntry de in d) + { + if (de.Value is Base b) + { + yield return b; + } + } + } + } + } +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBasePropertyGatherer.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBasePropertyGatherer.cs new file mode 100644 index 00000000..e6bef180 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleBasePropertyGatherer.cs @@ -0,0 +1,99 @@ +using System.Collections.Concurrent; +using System.Reflection; +using Speckle.InterfaceGenerator; +using Speckle.Newtonsoft.Json; +using Speckle.Sdk.Common; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Models; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +public readonly record struct Property(string Name, object? Value, PropertyAttributeInfo PropertyAttributeInfo); + +[GenerateAutoInterface] +public class SpeckleBasePropertyGatherer : ISpeckleBasePropertyGatherer +{ + private readonly ConcurrentDictionary> _typedPropertiesCache = + new(); + + public IEnumerable ExtractAllProperties(Base baseObj) + { + IReadOnlyList<(PropertyInfo, PropertyAttributeInfo)> typedProperties = GetTypedPropertiesWithCache(baseObj); + IReadOnlyCollection dynamicProperties = baseObj.DynamicPropertyKeys; + + // Construct `allProperties`: Add typed properties + foreach ((PropertyInfo propertyInfo, PropertyAttributeInfo detachInfo) in typedProperties) + { + object? baseValue = propertyInfo.GetValue(baseObj); + yield return new(propertyInfo.Name, baseValue, detachInfo); + } + + // Construct `allProperties`: Add dynamic properties + foreach (string propName in dynamicProperties) + { + if (propName.StartsWith("__")) + { + continue; + } + + object? baseValue = baseObj[propName]; + + bool isDetachable = PropNameValidator.IsDetached(propName); + + int chunkSize = 1000; + bool isChunkable = isDetachable && PropNameValidator.IsChunkable(propName, out chunkSize); + + yield return new(propName, baseValue, new PropertyAttributeInfo(isDetachable, isChunkable, chunkSize, null)); + } + } + + // (propertyInfo, isDetachable, isChunkable, chunkSize, JsonPropertyAttribute) + private IReadOnlyList<(PropertyInfo, PropertyAttributeInfo)> GetTypedPropertiesWithCache(Base baseObj) + { + Type type = baseObj.GetType(); + + if ( + _typedPropertiesCache.TryGetValue( + type.FullName.NotNull(), + out List<(PropertyInfo, PropertyAttributeInfo)>? cached + ) + ) + { + return cached; + } + + var typedProperties = baseObj.GetInstanceMembers().ToList(); + List<(PropertyInfo, PropertyAttributeInfo)> ret = new(typedProperties.Count); + + foreach (PropertyInfo typedProperty in typedProperties) + { + if (typedProperty.Name.StartsWith("__") || typedProperty.Name == "id") + { + continue; + } + + bool jsonIgnore = typedProperty.IsDefined(typeof(JsonIgnoreAttribute), false); + if (jsonIgnore) + { + continue; + } + + _ = typedProperty.GetValue(baseObj); + + List detachableAttributes = typedProperty + .GetCustomAttributes(true) + .ToList(); + List chunkableAttributes = typedProperty + .GetCustomAttributes(true) + .ToList(); + bool isDetachable = detachableAttributes.Count > 0 && detachableAttributes[0].Detachable; + bool isChunkable = chunkableAttributes.Count > 0; + int chunkSize = isChunkable ? chunkableAttributes[0].MaxObjCountPerChunk : 1000; + JsonPropertyAttribute? jsonPropertyAttribute = typedProperty.GetCustomAttribute(); + ret.Add((typedProperty, new PropertyAttributeInfo(isDetachable, isChunkable, chunkSize, jsonPropertyAttribute))); + } + + _typedPropertiesCache[type.FullName] = ret; + return ret; + } +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleObjectSerializer.cs new file mode 100644 index 00000000..ee98f643 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SpeckleObjectSerializer.cs @@ -0,0 +1,361 @@ +using System.Collections; +using System.Drawing; +using System.Globalization; +using Speckle.DoubleNumerics; +using Speckle.Newtonsoft.Json; +using Speckle.Sdk.Common; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Models; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +public class SpeckleObjectSerializer2 +{ + private HashSet _parentObjects = new(); + private readonly List> _childclosures; + + private readonly bool _trackDetachedChildren; + private readonly ISpeckleBasePropertyGatherer _propertyGatherer; + private readonly CancellationToken _cancellationToken; + + /// + /// Keeps track of all detached children created during serialisation that have an applicationId (provided this serializer instance has been told to track detached children). + /// This is currently used to cache previously converted objects and avoid their conversion if they haven't changed. See the DUI3 send bindings in rhino or another host app. + /// + public Dictionary ObjectReferences { get; } = new(); + + /// + /// Creates a new Serializer instance. + /// + /// Whether to store all detachable objects while serializing. They can be retrieved via post serialization. + /// + public SpeckleObjectSerializer2( + ISpeckleBasePropertyGatherer propertyGatherer, + List> childclosures, + bool trackDetachedChildren = false, + CancellationToken cancellationToken = default + ) + { + _childclosures = childclosures; + _propertyGatherer = propertyGatherer; + _cancellationToken = cancellationToken; + _trackDetachedChildren = trackDetachedChildren; + } + + /// The object to serialize + /// The serialized JSON + /// The serializer is busy (already serializing an object) + /// Failed to extract (pre-serialize) properties from the + public string Serialize(Base baseObj) + { + try + { + try + { + var result = SerializeBase(baseObj, true).NotNull(); + return result.Json; + } + catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) + { + throw new SpeckleSerializeException($"Failed to extract (pre-serialize) properties from the {baseObj}", ex); + } + } + finally + { + _parentObjects = new HashSet(); + } + } + + // `Preserialize` means transforming all objects into the final form that will appear in json, with basic .net objects + // (primitives, lists and dictionaries with string keys) + private void SerializeProperty( + object? obj, + JsonWriter writer, + bool computeClosures = false, + PropertyAttributeInfo inheritedDetachInfo = default + ) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (obj == null) + { + writer.WriteNull(); + return; + } + + if (obj.GetType().IsPrimitive || obj is string) + { + writer.WriteValue(obj); + return; + } + + switch (obj) + { + // Start with object references so they're not captured by the Base class case below + // Note: this change was needed as we've made the ObjectReference type inherit from Base for + // the purpose of the "do not convert unchanged previously converted objects" POC. + case ObjectReference r: + Dictionary ret = + new() + { + ["speckle_type"] = r.speckle_type, + ["referencedId"] = r.referencedId, + ["__closure"] = r.closure, + }; + if (r.closure is not null) + { + foreach (var kvp in r.closure) + { + UpdateChildClosures(kvp.Key); + } + } + UpdateChildClosures(r.referencedId); + SerializeProperty(ret, writer); + break; + case Base b: + var result = SerializeBase(b, computeClosures, inheritedDetachInfo); + if (result is not null) + { + writer.WriteRawValue(result.Json); + } + else + { + writer.WriteNull(); + } + break; + case IDictionary d: + { + writer.WriteStartObject(); + + foreach (DictionaryEntry kvp in d) + { + if (kvp.Key is not string key) + { + throw new ArgumentException( + "Serializing dictionaries that are not string based keys is not supported", + nameof(obj) + ); + } + + writer.WritePropertyName(key); + SerializeProperty(kvp.Value, writer, inheritedDetachInfo: inheritedDetachInfo); + } + writer.WriteEndObject(); + } + break; + case ICollection e: + { + writer.WriteStartArray(); + foreach (object? element in e) + { + SerializeProperty(element, writer, inheritedDetachInfo: inheritedDetachInfo); + } + writer.WriteEndArray(); + } + break; + case Enum: + writer.WriteValue((int)obj); + break; + // Support for simple types + case Guid g: + writer.WriteValue(g.ToString()); + break; + case Color c: + writer.WriteValue(c.ToArgb()); + break; + case DateTime t: + writer.WriteValue(t.ToString("o", CultureInfo.InvariantCulture)); + break; + case Matrix4x4 md: + writer.WriteStartArray(); + + writer.WriteValue(md.M11); + writer.WriteValue(md.M12); + writer.WriteValue(md.M13); + writer.WriteValue(md.M14); + writer.WriteValue(md.M21); + writer.WriteValue(md.M22); + writer.WriteValue(md.M23); + writer.WriteValue(md.M24); + writer.WriteValue(md.M31); + writer.WriteValue(md.M32); + writer.WriteValue(md.M33); + writer.WriteValue(md.M34); + writer.WriteValue(md.M41); + writer.WriteValue(md.M42); + writer.WriteValue(md.M43); + writer.WriteValue(md.M44); + writer.WriteEndArray(); + break; + //BACKWARDS COMPATIBILITY: matrix4x4 changed from System.Numerics float to System.DoubleNumerics double in release 2.16 + case System.Numerics.Matrix4x4: + throw new ArgumentException("Please use Speckle.DoubleNumerics.Matrix4x4 instead", nameof(obj)); + default: + throw new ArgumentException($"Unsupported value in serialization: {obj.GetType()}", nameof(obj)); + } + } + + private SerializationResult? SerializeBase( + Base baseObj, + bool computeClosures = false, + PropertyAttributeInfo inheritedDetachInfo = default + ) + { + // handle circular references + bool alreadySerialized = !_parentObjects.Add(baseObj); + if (alreadySerialized) + { + return null; + } + + Dictionary closure = new(); + string id; + string json; + lock (_childclosures) + { + if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) + { + _childclosures.Add(closure); + } + + using var writer = new StringWriter(); + using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); + id = SerializeBaseObject(baseObj, jsonWriter, closure); + json = writer.ToString(); + + if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) + { + _childclosures.RemoveAt(_childclosures.Count - 1); + } + } + + _parentObjects.Remove(baseObj); + + if (baseObj is Blob) + { + throw new NotSupportedException(); + /*StoreBlob(myBlob); + UpdateParentClosures($"blob:{id}"); + return new(json, id);*/ + } + + if (inheritedDetachInfo.IsDetachable) + { + ObjectReference objRef = new() { referencedId = id.NotNull() }; + using var writer2 = new StringWriter(); + using var jsonWriter2 = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer2); + SerializeProperty(objRef, jsonWriter2); + var json2 = writer2.ToString(); + UpdateChildClosures(id); + + // add to obj refs to return + if (baseObj.applicationId != null && _trackDetachedChildren) // && baseObj is not DataChunk && baseObj is not Abstract) // not needed, as data chunks will never have application ids, and abstract objs are not really used. + { + ObjectReferences[baseObj.applicationId] = new ObjectReference() + { + referencedId = id, + applicationId = baseObj.applicationId, + closure = closure, + }; + } + return new(json2, null); + } + return new(json.NotNull(), id); + } + + private string SerializeBaseObject(Base baseObj, JsonWriter writer, IReadOnlyDictionary closure) + { + if (baseObj is not Blob) + { + writer = new SerializerIdWriter(writer); + } + + writer.WriteStartObject(); + // Convert all properties + foreach (var prop in _propertyGatherer.ExtractAllProperties(baseObj)) + { + if (prop.PropertyAttributeInfo.JsonPropertyInfo is { NullValueHandling: NullValueHandling.Ignore }) + { + continue; + } + + writer.WritePropertyName(prop.Name); + SerializeProperty(prop.Value, writer, prop.PropertyAttributeInfo); + } + + string id; + if (writer is SerializerIdWriter serializerIdWriter) + { + (var json, writer) = serializerIdWriter.FinishIdWriter(); + id = IdGenerator.ComputeId(json); + } + else + { + id = ((Blob)baseObj).id; + } + writer.WritePropertyName("id"); + writer.WriteValue(id); + baseObj.id = id; + + if (closure.Count > 0) + { + writer.WritePropertyName("__closure"); + writer.WriteStartObject(); + foreach (var c in closure) + { + writer.WritePropertyName(c.Key); + writer.WriteValue(c.Value); + } + writer.WriteEndObject(); + } + + writer.WriteEndObject(); + return id; + } + + private void SerializeProperty(object? baseValue, JsonWriter jsonWriter, PropertyAttributeInfo detachInfo) + { + if (baseValue is IEnumerable chunkableCollection && detachInfo.IsChunkable) + { + List chunks = new(); + DataChunk crtChunk = new() { data = new List(detachInfo.ChunkSize) }; + + foreach (object element in chunkableCollection) + { + crtChunk.data.Add(element); + if (crtChunk.data.Count >= detachInfo.ChunkSize) + { + chunks.Add(crtChunk); + crtChunk = new DataChunk { data = new List(detachInfo.ChunkSize) }; + } + } + + if (crtChunk.data.Count > 0) + { + chunks.Add(crtChunk); + } + + SerializeProperty(chunks, jsonWriter, inheritedDetachInfo: new PropertyAttributeInfo(true, false, 0, null)); + return; + } + + SerializeProperty(baseValue, jsonWriter, inheritedDetachInfo: detachInfo); + } + + private void UpdateChildClosures(string objectId) + { + lock (_childclosures) + { + for (int i = 0; i < _childclosures.Count; i++) + { + int childDepth = _childclosures.Count - i; + if (!_childclosures[i].TryGetValue(objectId, out int currentValue)) + { + currentValue = childDepth; + } + + _childclosures[i][objectId] = Math.Min(currentValue, childDepth); + } + } + } +} diff --git a/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs index b20ed9b2..9f817d6c 100644 --- a/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs +++ b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs @@ -1,11 +1,15 @@ using System.Net; +using System.Net.Http.Headers; using System.Runtime.CompilerServices; using System.Text; using Speckle.InterfaceGenerator; using Speckle.Newtonsoft.Json; +using Speckle.Sdk.Common; +using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Helpers; using Speckle.Sdk.Logging; using Speckle.Sdk.Transports; +using Speckle.Sdk.Transports.ServerUtils; namespace Speckle.Sdk.Serialisation.V2; @@ -132,4 +136,79 @@ public class ServerObjectManager : IServerObjectManager } } } + + public async Task> HasObjects( + string streamId, + IReadOnlyList objectIds, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Stopwatch sw = new Stopwatch(); sw.Start(); + + string objectsPostParameter = JsonConvert.SerializeObject(objectIds); + var payload = new Dictionary { { "objects", objectsPostParameter } }; + string serializedPayload = JsonConvert.SerializeObject(payload); + var uri = new Uri($"/api/diff/{streamId}", UriKind.Relative); + + using StringContent stringContent = new(serializedPayload, Encoding.UTF8, "application/json"); + using HttpResponseMessage response = await _client + .PostAsync(uri, stringContent, cancellationToken) + .ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); +#if NET8_0_OR_GREATER + var hasObjects = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); +#else + var hasObjects = await response.Content.ReadAsStringAsync().ConfigureAwait(false); +#endif + return JsonConvert.DeserializeObject>(hasObjects).NotNull(); + } + + public async Task UploadObjects( + string streamId, + IReadOnlyList objects, + bool compressPayloads, + IProgress? progress, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + + using HttpRequestMessage message = + new() { RequestUri = new Uri($"/objects/{streamId}", UriKind.Relative), Method = HttpMethod.Post }; + + MultipartFormDataContent multipart = new(); + + int mpId = 0; + var ctBuilder = new StringBuilder("["); + for (int i = 0; i < objects.Count; i++) + { + if (i > 0) + { + ctBuilder.Append(','); + } + + ctBuilder.Append(objects[i].Json); + } + ctBuilder.Append(']'); + string ct = ctBuilder.ToString(); + + if (compressPayloads) + { + var content = new GzipContent(new StringContent(ct, Encoding.UTF8)); + content.Headers.ContentType = new MediaTypeHeaderValue("application/gzip"); + multipart.Add(content, $"batch-{mpId}", $"batch-{mpId}"); + } + else + { + multipart.Add(new StringContent(ct, Encoding.UTF8), $"batch-{mpId}", $"batch-{mpId}"); + } + + message.Content = new ProgressContent(multipart, progress); + HttpResponseMessage response = await _client.SendAsync(message, cancellationToken).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + } } diff --git a/src/Speckle.Sdk/SpeckleException.cs b/src/Speckle.Sdk/SpeckleException.cs index 77a61eb9..19e18fe4 100644 --- a/src/Speckle.Sdk/SpeckleException.cs +++ b/src/Speckle.Sdk/SpeckleException.cs @@ -1,6 +1,8 @@ namespace Speckle.Sdk; +#pragma warning disable CA2237 public class SpeckleException : Exception +#pragma warning restore CA2237 { public SpeckleException() { } diff --git a/src/Speckle.Sdk/Transports/DiskTransport.cs b/src/Speckle.Sdk/Transports/DiskTransport.cs index 4c74a8a1..2c60a36d 100644 --- a/src/Speckle.Sdk/Transports/DiskTransport.cs +++ b/src/Speckle.Sdk/Transports/DiskTransport.cs @@ -93,7 +93,6 @@ public class DiskTransport : ICloneable, ITransport } SavedObjectCount++; - OnProgressAction?.Report(new(ProgressEvent.DownloadObject, SavedObjectCount, null)); stopwatch.Stop(); Elapsed += stopwatch.Elapsed; } diff --git a/src/Speckle.Sdk/Transports/MemoryTransport.cs b/src/Speckle.Sdk/Transports/MemoryTransport.cs index 64a6ad1b..cc6aa1d5 100644 --- a/src/Speckle.Sdk/Transports/MemoryTransport.cs +++ b/src/Speckle.Sdk/Transports/MemoryTransport.cs @@ -85,7 +85,6 @@ public sealed class MemoryTransport : ITransport, ICloneable, IBlobCapableTransp _objects[id] = serializedObject; SavedObjectCount++; - OnProgressAction?.Report(new(ProgressEvent.UploadObject, 1, 1)); stopwatch.Stop(); Elapsed += stopwatch.Elapsed; } diff --git a/src/Speckle.Sdk/Transports/ProgressArgs.cs b/src/Speckle.Sdk/Transports/ProgressArgs.cs index f39a49bf..0e04cdc5 100644 --- a/src/Speckle.Sdk/Transports/ProgressArgs.cs +++ b/src/Speckle.Sdk/Transports/ProgressArgs.cs @@ -4,12 +4,14 @@ public readonly record struct ProgressArgs(ProgressEvent ProgressEvent, long Cou public enum ProgressEvent { - CacheCheck, - Cached, - DownloadBytes, + CachedToLocal, //send and receive + + FromCacheOrSerialized, UploadBytes, - DownloadObject, - UploadObject, + + CacheCheck, + DownloadBytes, DeserializeObject, - SerializeObject, + + SerializeObject, // old } diff --git a/src/Speckle.Sdk/Transports/SQLiteTransport.cs b/src/Speckle.Sdk/Transports/SQLiteTransport.cs index e9742e83..46b2985d 100644 --- a/src/Speckle.Sdk/Transports/SQLiteTransport.cs +++ b/src/Speckle.Sdk/Transports/SQLiteTransport.cs @@ -330,8 +330,6 @@ public sealed class SQLiteTransport : IDisposable, ICloneable, ITransport, IBlob CancellationToken.ThrowIfCancellationRequested(); } - OnProgressAction?.Report(new(ProgressEvent.DownloadObject, saved, _queue.Count + 1)); - CancellationToken.ThrowIfCancellationRequested(); if (!_queue.IsEmpty) diff --git a/src/Speckle.Sdk/Transports/TransportHelpers.cs b/src/Speckle.Sdk/Transports/TransportHelpers.cs index 9b738cee..abe2f075 100644 --- a/src/Speckle.Sdk/Transports/TransportHelpers.cs +++ b/src/Speckle.Sdk/Transports/TransportHelpers.cs @@ -31,7 +31,6 @@ public static class TransportHelpers var closures = ClosureParser.GetChildrenIds(parent).ToList(); - int i = 0; foreach (var closure in closures) { cancellationToken.ThrowIfCancellationRequested(); @@ -50,8 +49,6 @@ public static class TransportHelpers } targetTransport.SaveObject(closure, child); - var count = i++; - sourceTransport.OnProgressAction?.Report(new ProgressArgs(ProgressEvent.UploadObject, count, closures.Count)); } return parent; diff --git a/tests/Speckle.Sdk.Serialization.Testing/DummyServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Testing/DummyServerObjectManager.cs new file mode 100644 index 00000000..2d1f4ebf --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Testing/DummyServerObjectManager.cs @@ -0,0 +1,47 @@ +using System.Text; +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Serialization.Testing; + +public class DummyServerObjectManager : IServerObjectManager +{ + public IAsyncEnumerable<(string, string)> DownloadObjects( + string streamId, + IReadOnlyList objectIds, + IProgress? progress, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task DownloadSingleObject( + string streamId, + string objectId, + IProgress? progress, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task> HasObjects( + string streamId, + IReadOnlyList objectIds, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task UploadObjects( + string streamId, + IReadOnlyList objects, + bool compressPayloads, + IProgress? progress, + CancellationToken cancellationToken + ) + { + long totalBytes = 0; + foreach (var item in objects) + { + totalBytes += Encoding.Default.GetByteCount(item.Json); + } + + progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes)); + return Task.CompletedTask; + } +} diff --git a/tests/Speckle.Sdk.Serialization.Testing/Program.cs b/tests/Speckle.Sdk.Serialization.Testing/Program.cs index 8914c981..0faa7339 100644 --- a/tests/Speckle.Sdk.Serialization.Testing/Program.cs +++ b/tests/Speckle.Sdk.Serialization.Testing/Program.cs @@ -1,12 +1,14 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Speckle.Sdk; +using Speckle.Sdk.Credentials; 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.Serialisation.V2.Send; using Speckle.Sdk.Serialization.Testing; const bool skipCache = false; @@ -17,6 +19,10 @@ 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/"; //other? +var streamId = "368f598929"; +var rootId = "67374cfe689c43ff8be12090af122244";*/ var url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? @@ -28,19 +34,30 @@ serviceCollection.AddSpeckleSdk(HostApplications.Navisworks, HostAppVersion.v202 var serviceProvider = serviceCollection.BuildServiceProvider(); Console.WriteLine("Attach"); -Console.ReadLine(); -Console.WriteLine("Executing"); +var token = serviceProvider.GetRequiredService().GetDefaultAccount()?.token; var progress = new Progress(true); -var sqliteTransport = new SQLiteCacheManager(streamId); +var sqliteTransport = new SQLiteReceiveCacheManager(streamId); var serverObjects = new ServerObjectManager( serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), new Uri(url), - null + token ); var o = new ObjectLoader(sqliteTransport, serverObjects, streamId, progress); var process = new DeserializeProcess(progress, o); -await process.Deserialize(rootId, default, new(skipCache)).ConfigureAwait(false); +var @base = await process.Deserialize(rootId, default, new(skipCache)).ConfigureAwait(false); +Console.WriteLine("Deserialized"); +Console.ReadLine(); +Console.WriteLine("Executing"); + +var process2 = new SerializeProcess( + progress, + new SQLiteSendCacheManager(streamId), + new DummyServerObjectManager(), + new SpeckleBaseChildFinder(new SpeckleBasePropertyGatherer()), + new SpeckleBasePropertyGatherer() +); +await process2.Serialize(streamId, @base, default, new SerializeProcessOptions(skipCache, true)).ConfigureAwait(false); Console.WriteLine("Detach"); Console.ReadLine(); diff --git a/tests/Speckle.Sdk.Serialization.Testing/Progress.cs b/tests/Speckle.Sdk.Serialization.Testing/Progress.cs index b0a4f663..03476f5a 100644 --- a/tests/Speckle.Sdk.Serialization.Testing/Progress.cs +++ b/tests/Speckle.Sdk.Serialization.Testing/Progress.cs @@ -4,7 +4,7 @@ namespace Speckle.Sdk.Serialization.Testing; public class Progress(bool write) : IProgress { - private readonly TimeSpan DEBOUNCE = TimeSpan.FromMilliseconds(500); + private readonly TimeSpan DEBOUNCE = TimeSpan.FromSeconds(1); private DateTime _lastTime = DateTime.UtcNow; private long _totalBytes; diff --git a/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs b/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs new file mode 100644 index 00000000..6630f219 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs @@ -0,0 +1,40 @@ +using Speckle.Sdk.Models; + +namespace Speckle.Sdk.Serialization.Tests; + +public class BaseComparer : IEqualityComparer +{ + public bool Equals(Base? x, Base? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null) + return false; + if (y is null) + return false; + Type type = x.GetType(); + if (type != y.GetType()) + return false; + var types = DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic | DynamicBaseMemberType.SchemaIgnored; + var membersX = x.GetMembers(types); + var membersY = y.GetMembers(types); + if (membersX.Count != membersY.Count) + return false; + foreach (var kvp in membersX) + { + var propertyInfo = type.GetProperty(kvp.Key); + if (propertyInfo is not null && !propertyInfo.CanWrite) + { + continue; + } + if (y[kvp.Key] != kvp.Value) + return false; + } + return x.id == y.id && x.applicationId == y.applicationId; + } + + public int GetHashCode(Base obj) + { + return HashCode.Combine(obj.id, obj.applicationId); + } +} diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs new file mode 100644 index 00000000..83079128 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -0,0 +1,250 @@ +using System.Collections.Concurrent; +using System.Text; +using NUnit.Framework; +using Shouldly; +using Speckle.Newtonsoft.Json.Linq; +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation; +using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Serialisation.V2.Send; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Serialization.Tests; + +public class DetachedTests +{ + [SetUp] + public void Setup() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly); + } + + [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + public async Task CanSerialize_New_Detached() + { + var expectedJson = """ + { + "list": [], + "arr": null, + "detachedProp": { + "speckle_type": "reference", + "referencedId": "d3dd4621b2f68c3058c2b9c023a9de19", + "__closure": null + }, + "attachedProp": { + "name": "attachedProp", + "applicationId": null, + "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase", + "id": "90d58b65c9036a8bc50743f4c71c1c2e" + }, + "crazyProp": null, + "applicationId": null, + "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase", + "dynamicProp": 123, + "id": "9ff8efb13c62fa80f3d1c4519376ba13", + "__closure": { + "d3dd4621b2f68c3058c2b9c023a9de19": 1 + } + } + """; + var detachedJson = """ + { + "name": "detachedProp", + "applicationId": null, + "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase", + "id": "d3dd4621b2f68c3058c2b9c023a9de19" + } + """; + var @base = new SampleObjectBase(); + @base["dynamicProp"] = 123; + @base.detachedProp = new SamplePropBase() { name = "detachedProp" }; + @base.attachedProp = new SamplePropBase() { name = "attachedProp" }; + + var objects = new Dictionary(); + + var process2 = new SerializeProcess( + null, + new DummySendCacheManager(objects), + new DummyServerObjectManager(), + new SpeckleBaseChildFinder(new SpeckleBasePropertyGatherer()), + new SpeckleBasePropertyGatherer() + ); + await process2 + .Serialize(string.Empty, @base, default, new SerializeProcessOptions(false, true)) + .ConfigureAwait(false); + + objects.Count.ShouldBe(2); + objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").ShouldBeTrue(); + objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").ShouldBeTrue(); + JToken + .DeepEquals(JObject.Parse(expectedJson), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])) + .ShouldBeTrue(); + JToken + .DeepEquals(JObject.Parse(detachedJson), JObject.Parse(objects["d3dd4621b2f68c3058c2b9c023a9de19"])) + .ShouldBeTrue(); + } + + [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + public void CanSerialize_Old_Detached() + { + var expectedJson = """ + { + "list": [], + "arr": null, + "detachedProp": { + "speckle_type": "reference", + "referencedId": "d3dd4621b2f68c3058c2b9c023a9de19", + "__closure": null + }, + "attachedProp": { + "name": "attachedProp", + "applicationId": null, + "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase", + "id": "90d58b65c9036a8bc50743f4c71c1c2e" + }, + "crazyProp": null, + "applicationId": null, + "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase", + "dynamicProp": 123, + "id": "9ff8efb13c62fa80f3d1c4519376ba13", + "__closure": { + "d3dd4621b2f68c3058c2b9c023a9de19": 1 + } + } + """; + var detachedJson = """ + { + "name": "detachedProp", + "applicationId": null, + "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase", + "id": "d3dd4621b2f68c3058c2b9c023a9de19" + } + """; + var @base = new SampleObjectBase(); + @base["dynamicProp"] = 123; + @base.detachedProp = new SamplePropBase() { name = "detachedProp" }; + @base.attachedProp = new SamplePropBase() { name = "attachedProp" }; + + var objects = new ConcurrentDictionary(); + var serializer = new SpeckleObjectSerializer(new[] { new MemoryTransport(objects) }); + var json = serializer.Serialize(@base); + + objects.Count.ShouldBe(2); + objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").ShouldBeTrue(); + objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(json), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])).ShouldBeTrue(); + JToken + .DeepEquals(JObject.Parse(expectedJson), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])) + .ShouldBeTrue(); + JToken + .DeepEquals(JObject.Parse(detachedJson), JObject.Parse(objects["d3dd4621b2f68c3058c2b9c023a9de19"])) + .ShouldBeTrue(); + } + + [Test] + public void GetPropertiesExpected() + { + var @base = new SampleObjectBase(); + @base["dynamicProp"] = 123; + @base["@prop2"] = 2; + @base["__prop3"] = 3; + @base.detachedProp = new SamplePropBase() { name = "detachedProp" }; + @base.attachedProp = new SamplePropBase() { name = "attachedProp" }; + + var children = new SpeckleBasePropertyGatherer().ExtractAllProperties(@base).ToList(); + + children.Count.ShouldBe(9); + children.First(x => x.Name == "dynamicProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); + children.First(x => x.Name == "attachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); + children.First(x => x.Name == "crazyProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); + children.First(x => x.Name == "speckle_type").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); + children.First(x => x.Name == "applicationId").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); + + children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); + children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); + children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); + children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); + } +} + +[SpeckleType("Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase")] +public class SampleObjectBase : Base +{ + [Chunkable, DetachProperty] + public List list { get; set; } = new(); + + [Chunkable(300), DetachProperty] + public double[] arr { get; set; } + + [DetachProperty] + public SamplePropBase detachedProp { get; set; } + + public SamplePropBase attachedProp { get; set; } + + public string crazyProp { get; set; } +} + +[SpeckleType("Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase")] +public class SamplePropBase : Base +{ + public string name { get; set; } +} + +public class DummyServerObjectManager : IServerObjectManager +{ + public IAsyncEnumerable<(string, string)> DownloadObjects( + string streamId, + IReadOnlyList objectIds, + IProgress? progress, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task DownloadSingleObject( + string streamId, + string objectId, + IProgress? progress, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task> HasObjects( + string streamId, + IReadOnlyList objectIds, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task UploadObjects( + string streamId, + IReadOnlyList objects, + bool compressPayloads, + IProgress? progress, + CancellationToken cancellationToken + ) + { + long totalBytes = 0; + foreach (var item in objects) + { + totalBytes += Encoding.Default.GetByteCount(item.Json); + } + + progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes)); + return Task.CompletedTask; + } +} + +public class DummySendCacheManager(Dictionary objects) : ISQLiteSendCacheManager +{ + public string? GetObject(string id) => null; + + public bool HasObject(string objectId) => false; + + public void SaveObjects(List items) + { + foreach (var item in items) + { + objects.Add(item.Id, item.Json); + } + } +} diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs new file mode 100644 index 00000000..ba43e279 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs @@ -0,0 +1,59 @@ +using System.Runtime.CompilerServices; +using System.Text; +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Serialization.Tests; + +public class DummyReceiveServerObjectManager(Dictionary objects) : IServerObjectManager +{ + public async IAsyncEnumerable<(string, string)> DownloadObjects( + string streamId, + IReadOnlyList objectIds, + IProgress? progress, + [EnumeratorCancellation] CancellationToken cancellationToken + ) + { + await Task.CompletedTask; + foreach (var id in objectIds) + { + yield return (id, objects[id]); + } + } + + public async Task DownloadSingleObject( + string streamId, + string objectId, + IProgress? progress, + CancellationToken cancellationToken + ) + { + await Task.CompletedTask; + return objects[objectId]; + } + + public Task> HasObjects( + string streamId, + IReadOnlyList objectIds, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task UploadObjects( + string streamId, + IReadOnlyList objects, + bool compressPayloads, + IProgress? progress, + CancellationToken cancellationToken + ) + { + long totalBytes = 0; + foreach (var item in objects) + { + totalBytes += Encoding.Default.GetByteCount(item.Json); + } + + progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes)); + return Task.CompletedTask; + } +} diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs new file mode 100644 index 00000000..b425da57 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs @@ -0,0 +1,58 @@ +using System.Collections.Concurrent; +using Shouldly; +using Speckle.Newtonsoft.Json.Linq; +using Speckle.Sdk.Common; +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Serialization.Tests; + +public class DummySendServerObjectManager(ConcurrentDictionary savedObjects) : IServerObjectManager +{ + public IAsyncEnumerable<(string, string)> DownloadObjects( + string streamId, + IReadOnlyList objectIds, + IProgress? progress, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task DownloadSingleObject( + string streamId, + string objectId, + IProgress? progress, + CancellationToken cancellationToken + ) => throw new NotImplementedException(); + + public Task> HasObjects( + string streamId, + IReadOnlyList objectIds, + CancellationToken cancellationToken + ) + { + return Task.FromResult(objectIds.ToDictionary(x => x, x => false)); + } + + public Task UploadObjects( + string streamId, + IReadOnlyList objects, + bool compressPayloads, + IProgress? progress, + CancellationToken cancellationToken + ) + { + foreach (var obj in objects) + { + obj.Id.ShouldBe(JObject.Parse(obj.Json)["id"].NotNull().Value()); + if (savedObjects.TryGetValue(obj.Id, out var j)) + { + j.ShouldBe(obj.Json); + } + else + { + savedObjects.TryAdd(obj.Id, obj.Json); + } + } + return Task.CompletedTask; + } +} diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs new file mode 100644 index 00000000..1b5c1ae2 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs @@ -0,0 +1,13 @@ +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Serialisation.V2; + +namespace Speckle.Sdk.Serialization.Tests; + +public class DummySqLiteReceiveManager(Dictionary savedObjects) : ISQLiteReceiveCacheManager +{ + public string? GetObject(string id) => savedObjects.GetValueOrDefault(id); + + public void SaveObject(BaseItem item) => throw new NotImplementedException(); + + public bool HasObject(string objectId) => throw new NotImplementedException(); +} diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs new file mode 100644 index 00000000..d0bb0fa4 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs @@ -0,0 +1,13 @@ +using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Serialisation.V2; + +namespace Speckle.Sdk.Serialization.Tests; + +public class DummySqLiteSendManager : ISQLiteSendCacheManager +{ + public string? GetObject(string id) => throw new NotImplementedException(); + + public bool HasObject(string objectId) => throw new NotImplementedException(); + + public void SaveObjects(List items) => throw new NotImplementedException(); +} diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs index f6cbb628..beac512d 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs @@ -1,7 +1,9 @@ +using System.Collections.Concurrent; using System.IO.Compression; using System.Reflection; using NUnit.Framework; using Shouldly; +using Speckle.Newtonsoft.Json; using Speckle.Newtonsoft.Json.Linq; using Speckle.Objects.BuiltElements; using Speckle.Sdk.Common; @@ -10,6 +12,7 @@ using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; using Speckle.Sdk.Serialisation.Utilities; using Speckle.Sdk.Serialisation.V2.Receive; +using Speckle.Sdk.Serialisation.V2.Send; namespace Speckle.Sdk.Serialization.Tests; @@ -164,4 +167,97 @@ public class SerializationTests starts.ShouldBeTrue($"{name} isn't expected"); } } + + [TestCase( + "{\"applicationId\":null,\"speckle_type\":\"Base\",\"IFC_GUID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcGUID\",\"units\":null,\"value\":\"18HX_ys0P5uu77f1wwA7bn\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_GUID\",\"id\":\"1f4e29b7198e25221300c684876ec187\"},\"DOOR_COST\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Cost\",\"units\":\"\u0e3f\",\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:currency-1.0.0\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DOOR_COST\",\"id\":\"80ff4c5df5170b75916a873a394cfbdf\"},\"ALL_MODEL_URL\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"URL\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_URL\",\"id\":\"140c53fcea5deaa35115b23cd2ba48c6\"},\"IFC_TYPE_GUID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcGUID\",\"units\":null,\"value\":\"0w69BRwHvBsBXN3bEBjQin\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_TYPE_GUID\",\"id\":\"99d5d914df5c50c879e73c50246a9249\"},\"KEYNOTE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Keynote\",\"units\":null,\"value\":\"S0905\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"KEYNOTE_PARAM\",\"id\":\"c2272311800b04ab4d2b0052df68ecdc\"},\"PHASE_CREATED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Phase Created\",\"units\":null,\"value\":\"0\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"PHASE_CREATED\",\"id\":\"72ecbbd5d29ea1b48df89d8f88b29120\"},\"ALL_MODEL_MARK\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Mark\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MARK\",\"id\":\"f2e0ed6ebfbab4d4780c5143b774558e\"},\"FUNCTION_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Function\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FUNCTION_PARAM\",\"id\":\"a43000484f3fa3c5cf60a2ccd79a573c\"},\"UNIFORMAT_CODE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Assembly Code\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"UNIFORMAT_CODE\",\"id\":\"b797bb20d49af57eecbe718df4ebd411\"},\"WINDOW_TYPE_ID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Mark\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"WINDOW_TYPE_ID\",\"id\":\"d74f026a13a539bd24369ea78b34aa6b\"},\"ALL_MODEL_IMAGE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Image\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_IMAGE\",\"id\":\"4ef25c5fcd2ee32d9b3d6ce9b1047904\"},\"ALL_MODEL_MODEL\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Model\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MODEL\",\"id\":\"13597261389f532c0778e134623eff85\"},\"CLEAR_COVER_TOP\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Top Face\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_TOP\",\"id\":\"ed5f5e056314ee8435a1658a54261e94\"},\"ELEM_TYPE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_TYPE_PARAM\",\"id\":\"e502cb5aed1fda357926c7ca9927c42c\"},\"RELATED_TO_MASS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Related to Mass\",\"units\":null,\"value\":false,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"RELATED_TO_MASS\",\"id\":\"a43b8424564b8f14738f4dbaa78be150\"},\"SYMBOL_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Id\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SYMBOL_ID_PARAM\",\"id\":\"1947cb98e61f79da57f573a3a785b436\"},\"DESIGN_OPTION_ID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Design Option\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DESIGN_OPTION_ID\",\"id\":\"cf30f731c41543dd134a4877fbdab105\"},\"PHASE_DEMOLISHED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Phase Demolished\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"PHASE_DEMOLISHED\",\"id\":\"47066bac7728f9b93f4acdb697284a59\"},\"CLEAR_COVER_OTHER\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Other Faces\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_OTHER\",\"id\":\"1afe7d22a897aff809bd92aea1acafd2\"},\"ELEM_FAMILY_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_FAMILY_PARAM\",\"id\":\"ec3a159572a58b85b3a3650e5cc23e90\"},\"CLEAR_COVER_BOTTOM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Bottom Face\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_BOTTOM\",\"id\":\"476161776fc4c6ceb3c544c792a08120\"},\"HOST_AREA_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Area\",\"units\":\"m\u00b2\",\"value\":7.128858225722908,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_AREA_COMPUTED\",\"id\":\"5e7567fea07a98c0cdd4903cabd897a3\"},\"IFC_EXPORT_ELEMENT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export to IFC\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT\",\"id\":\"7de623e201f3fcfb16dbcacefe7f8403\"},\"totalChildrenCount\":0,\"ALL_MODEL_TYPE_NAME\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Name\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_NAME\",\"id\":\"4699f3fc2fd4e84cc3b6296ded7225b5\"},\"DESIGN_OPTION_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Design Option\",\"units\":null,\"value\":\"Main Model\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DESIGN_OPTION_PARAM\",\"id\":\"771449eae7f2fb96345b165954b2c797\"},\"ELEM_CATEGORY_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Category\",\"units\":null,\"value\":\"-2000032\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_CATEGORY_PARAM\",\"id\":\"fb0b948f7360b9415ea9ede20fb3cdd2\"},\"ALL_MODEL_TYPE_IMAGE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Image\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_IMAGE\",\"id\":\"4c95be61c11f5609f1fa649804bf9814\"},\"ANALYTICAL_ROUGHNESS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Roughness\",\"units\":null,\"value\":1,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ROUGHNESS\",\"id\":\"eab64895ad089cf272ae6a7431f4cdac\"},\"HOST_VOLUME_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Volume\",\"units\":\"m\u00b3\",\"value\":1.413211687704679,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:cubicMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_VOLUME_COMPUTED\",\"id\":\"4cac83d2757bc70d7e1f299de124d028\"},\"SCHEDULE_LEVEL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level\",\"units\":null,\"value\":\"1100600\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SCHEDULE_LEVEL_PARAM\",\"id\":\"d0ab715757ddbaedf5dc2df0726ed38c\"},\"ALL_MODEL_DESCRIPTION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Description\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_DESCRIPTION\",\"id\":\"4abdaadbe23c12c349c65abcd5979f56\"},\"IFC_EXPORT_ELEMENT_AS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export to IFC As\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_AS\",\"id\":\"ccacac43b32ffd10c88a870492f98f96\"},\"UNIFORMAT_DESCRIPTION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Assembly Description\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"UNIFORMAT_DESCRIPTION\",\"id\":\"abfe7173561e8b86cae8aa8dc34743d1\"},\"ALL_MODEL_MANUFACTURER\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Manufacturer\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MANUFACTURER\",\"id\":\"e280ae740be9133f1001f218a137bb2f\"},\"ANALYTICAL_ABSORPTANCE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Absorptance\",\"units\":null,\"value\":0.1,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ABSORPTANCE\",\"id\":\"e1618d04224fb3e11e650f8854e5eddb\"},\"ELEM_CATEGORY_PARAM_MT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Category\",\"units\":null,\"value\":\"-2000032\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_CATEGORY_PARAM_MT\",\"id\":\"d4119e43880a3cc8632a137d4f3372ae\"},\"ALL_MODEL_TYPE_COMMENTS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Comments\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_COMMENTS\",\"id\":\"8ea15d6198e1f5c632df36270be5433e\"},\"ANALYTICAL_THERMAL_MASS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thermal Mass\",\"units\":\"kJ/(m\u00b2·K)\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:kilojoulesPerSquareMeterKelvin-1.0.0\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_THERMAL_MASS\",\"id\":\"d8b711b81d9e0ad7f072b60b69bd0239\"},\"HOST_PERIMETER_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Perimeter\",\"units\":\"mm\",\"value\":11098.801755409942,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_PERIMETER_COMPUTED\",\"id\":\"eb73365794668bf73b3ffd2c80162ee1\"},\"IFC_EXPORT_ELEMENT_TYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export Type to IFC\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_TYPE\",\"id\":\"0a96867bd313e951c229fb92b346b516\"},\"WALL_ATTR_ROOM_BOUNDING\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Room Bounding\",\"units\":null,\"value\":true,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"WALL_ATTR_ROOM_BOUNDING\",\"id\":\"fdf5bd19ac0a9f2878323c71e4ae80ea\"},\"FLOOR_STRUCTURE_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Structure\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_STRUCTURE_ID_PARAM\",\"id\":\"43b858d8cfaf2bd27cb0b466dc6d425b\"},\"SYMBOL_FAMILY_NAME_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family Name\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SYMBOL_FAMILY_NAME_PARAM\",\"id\":\"d96f492f43f2b0e11ce86d66c23caf0f\"},\"IFC_EXPORT_PREDEFINEDTYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IFC Predefined Type\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_PREDEFINEDTYPE\",\"id\":\"b927074616bc0e6e323b52a99867b907\"},\"STRUCTURAL_MATERIAL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Structural Material\",\"units\":null,\"value\":\"215194\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_MATERIAL_PARAM\",\"id\":\"1f7ffc00602d1944892885d68dff8867\"},\"ELEM_FAMILY_AND_TYPE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family and Type\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_FAMILY_AND_TYPE_PARAM\",\"id\":\"9d58f36db73c0c248a6db682a6c6a6a0\"},\"FLOOR_ATTR_THICKNESS_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thickness\",\"units\":\"mm\",\"value\":200,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_ATTR_THICKNESS_PARAM\",\"id\":\"22c96d409372e700936805b825b574e6\"},\"IFC_EXPORT_ELEMENT_TYPE_AS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export Type to IFC As\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_TYPE_AS\",\"id\":\"41a53fec385581b6af53942aff3cd2d3\"},\"ALL_MODEL_INSTANCE_COMMENTS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Comments\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_INSTANCE_COMMENTS\",\"id\":\"ca8cdcc0b3fd824a34dcb42749151cd1\"},\"STRUCTURAL_ELEVATION_AT_TOP\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top\",\"units\":\"mm\",\"value\":21099.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP\",\"id\":\"b1f293a63ff03c0c7456f8ba7b703f4f\"},\"FLOOR_HEIGHTABOVELEVEL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Height Offset From Level\",\"units\":\"mm\",\"value\":-100,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_HEIGHTABOVELEVEL_PARAM\",\"id\":\"312773813c84648fc5ff2d78a8d8d8bc\"},\"ANALYTICAL_THERMAL_RESISTANCE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thermal Resistance (R)\",\"units\":\"(m\u00b2·K)/W\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:squareMeterKelvinsPerWatt-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_THERMAL_RESISTANCE\",\"id\":\"fcfa5d36d656d4f8ca2b883a17c310b8\"},\"IFC_EXPORT_PREDEFINEDTYPE_TYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IFC Predefined Type\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_PREDEFINEDTYPE_TYPE\",\"id\":\"ac166cbccbcd8335272956f09d8d5d42\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom\",\"units\":\"mm\",\"value\":20899.9999999999,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM\",\"id\":\"d9e8f0e4b57b00ca99d13df99ea6ac26\"},\"COARSE_SCALE_FILL_PATTERN_COLOR\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Coarse Scale Fill Color\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"COARSE_SCALE_FILL_PATTERN_COLOR\",\"id\":\"854d889fd71071f3b81d0e06f7f1095c\"},\"STRUCTURAL_FLOOR_CORE_THICKNESS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Core Thickness\",\"units\":\"mm\",\"value\":199.99999999999784,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_FLOOR_CORE_THICKNESS\",\"id\":\"6fb6fb65a394c5c68d5a760289c1129d\"},\"STRUCTURAL_ELEVATION_AT_TOP_CORE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top Core\",\"units\":\"mm\",\"value\":21099.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP_CORE\",\"id\":\"aed8eedfb2527594e14ae4e5f74fb5c1\"},\"ANALYTICAL_ELEMENT_HAS_ASSOCIATION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Has Association\",\"units\":null,\"value\":true,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ELEMENT_HAS_ASSOCIATION\",\"id\":\"c9a6f771f05ef6072100c59c672dfb77\"},\"COARSE_SCALE_FILL_PATTERN_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Coarse Scale Fill Pattern\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"COARSE_SCALE_FILL_PATTERN_ID_PARAM\",\"id\":\"1d271f4d80ffe772f9f8896971050ccc\"},\"FLOOR_ATTR_DEFAULT_THICKNESS_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Default Thickness\",\"units\":\"mm\",\"value\":200,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_ATTR_DEFAULT_THICKNESS_PARAM\",\"id\":\"271b8b7f7e29c45065c1ccaa1095b32e\"},\"STRUCTURAL_ELEVATION_AT_TOP_SURVEY\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top Survey\",\"units\":\"mm\",\"value\":30899.999999999894,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP_SURVEY\",\"id\":\"8f2b9f55e373736263d14002838194b4\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM_CORE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom Core\",\"units\":\"mm\",\"value\":20899.9999999999,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM_CORE\",\"id\":\"7a0b7e496383d08605ccb8c776cedbbf\"},\"02b58af4-afcd-404b-9011-3a25d6816e1b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"02b58af4-afcd-404b-9011-3a25d6816e1b\",\"id\":\"141f6b021c701e5b4b4ee430652f7f91\"},\"042673e7-8ac4-413d-a393-e0785fbf8889\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"042673e7-8ac4-413d-a393-e0785fbf8889\",\"id\":\"f415ff5c972f45d7e0090b0849c54677\"},\"07afa150-f11f-40a1-a173-7a77ea32cf96\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"07afa150-f11f-40a1-a173-7a77ea32cf96\",\"id\":\"0e3acdf0b385d32d68caa8753c710849\"},\"07b6cf99-a3d2-4d7a-9ea4-246058cfae1a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.22.Description\",\"units\":null,\"value\":\"High-Tolerance Concrete Floor Finishing\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"07b6cf99-a3d2-4d7a-9ea4-246058cfae1a\",\"id\":\"8957914412a138b6255452dd485a25bd\"},\"082d16bb-7cf9-4968-ac22-b6f6ae068028\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"082d16bb-7cf9-4968-ac22-b6f6ae068028\",\"id\":\"f313bddfb2c5cf9f825ee6653021b04e\"},\"087f96a5-2dd2-42bb-a170-c22485216c09\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Element Condition\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"087f96a5-2dd2-42bb-a170-c22485216c09\",\"id\":\"8665b9cec7a39bffa030e6b415f78fa9\"},\"098e8d4f-1431-49e8-8ef6-69516cf72354\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"3D Model Element GUID\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"098e8d4f-1431-49e8-8ef6-69516cf72354\",\"id\":\"b81c608ec3bd9aa08ea1a2c5a2cea206\"},\"0a004b99-d4e6-4db6-8c88-9b77da33f012\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0a004b99-d4e6-4db6-8c88-9b77da33f012\",\"id\":\"79b720b93988fd7840213380399408dd\"},\"0bf3a5d2-06c0-4b6c-9ba1-6985ef40c2b0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0bf3a5d2-06c0-4b6c-9ba1-6985ef40c2b0\",\"id\":\"622eab526502ce2a848c4aa932554f96\"},\"0c273fd8-260b-4f34-996e-921fa14a47fc\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0c273fd8-260b-4f34-996e-921fa14a47fc\",\"id\":\"f70edec21839dfc410d94659d18d52c2\"},\"11f34dfe-4592-4c86-a455-2f020d9376e8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"11f34dfe-4592-4c86-a455-2f020d9376e8\",\"id\":\"cd817060f1920a0194139bf2cdddecd4\"},\"12e4c976-0b76-4735-8664-e882b410ac7e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"12e4c976-0b76-4735-8664-e882b410ac7e\",\"id\":\"0d2df557e73500e0c3d72c527d4c36fe\"},\"1336888e-1fed-4e9e-b74b-794bff5b6046\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"GrossArea(BaseQuantities)\",\"units\":\"m\u00b2\",\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1336888e-1fed-4e9e-b74b-794bff5b6046\",\"id\":\"7e0f5932a6c5ad37872436b3ed0cf07b\"},\"15212817-1c39-4c7f-bae4-436acd0e4598\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"15212817-1c39-4c7f-bae4-436acd0e4598\",\"id\":\"f2bba2dcccf5786df17c279367baab39\"},\"18a3daed-8579-45e2-97a0-412159986104\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 8\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"18a3daed-8579-45e2-97a0-412159986104\",\"id\":\"173b9745cf4e7cc4b67748c5a03acf04\"},\"18ab825f-ba4c-4a8f-b509-5ddd7c378267\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Item\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"18ab825f-ba4c-4a8f-b509-5ddd7c378267\",\"id\":\"2c130ba41bf9797a0e7e64315695dd7b\"},\"1948bc31-5a23-482a-b337-4bd1fce08aec\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Renovation Status(AC_Pset_RenovationAndPhasing)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1948bc31-5a23-482a-b337-4bd1fce08aec\",\"id\":\"59b7e7b981f77ff4e7b01e7d794234f9\"},\"1d9a0983-608b-4aed-b03f-f27e8e0e677a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1d9a0983-608b-4aed-b03f-f27e8e0e677a\",\"id\":\"ec604873c2b3c4d53f5ea9c2e203fc70\"},\"219c8c15-4722-4b40-9e19-7fbbddeee30f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"219c8c15-4722-4b40-9e19-7fbbddeee30f\",\"id\":\"abc83e3214698cefe7bbd9326b536b1d\"},\"226b84c2-b3b5-4a04-93f7-9523a21ef4e0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Discipline\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"226b84c2-b3b5-4a04-93f7-9523a21ef4e0\",\"id\":\"f0e8a4a841062c6b5517b30002fd2325\"},\"244a8c27-edd6-4b09-8905-4cf403c61235\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Schedule Type Code/Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"244a8c27-edd6-4b09-8905-4cf403c61235\",\"id\":\"1ff35615eaa5e568fdb3c7c1bb21b72a\"},\"2cbf5041-2b36-4c7f-b65e-439af251d9f7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Equipment Type\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2cbf5041-2b36-4c7f-b65e-439af251d9f7\",\"id\":\"1339498cfd39967f51d7d5a31feba974\"},\"2eac0fd8-0c8a-4c5a-9d54-62415d708f37\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elemental Code\",\"units\":null,\"value\":\"UFSB\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2eac0fd8-0c8a-4c5a-9d54-62415d708f37\",\"id\":\"e20483f71786c3e27291a3eeaa049b48\"},\"2f1ef0a4-09a2-4e80-ba30-57984a475e1d\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2f1ef0a4-09a2-4e80-ba30-57984a475e1d\",\"id\":\"d083dfba92681370be68f19d761a9628\"},\"2fb9b7d9-d0b0-4ce2-bbc0-02464fda354c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2fb9b7d9-d0b0-4ce2-bbc0-02464fda354c\",\"id\":\"71196d8fbc135c7386841bb439f8aaee\"},\"3490690f-a8be-46d9-9607-47c255e9ee89\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3490690f-a8be-46d9-9607-47c255e9ee89\",\"id\":\"cf2c87ffe9b6babcd2659d619fe3a4b7\"},\"35064971-5814-4b17-b572-49ea1320c516\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"35064971-5814-4b17-b572-49ea1320c516\",\"id\":\"ecf761dcb34a3e098f355f401eec5738\"},\"36d9a077-9301-47a0-b049-4f29e17d51dd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"36d9a077-9301-47a0-b049-4f29e17d51dd\",\"id\":\"f547ba1d204747353092fccb1970b8ee\"},\"392cae56-cd6b-4946-817f-242686e12441\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Zone\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"392cae56-cd6b-4946-817f-242686e12441\",\"id\":\"37152ed7e04c0ea23c442aad0be9a611\"},\"3c3d55ea-8a2f-41c1-97fd-d222d586b0b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Omniclass Classification Type Code\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3c3d55ea-8a2f-41c1-97fd-d222d586b0b1\",\"id\":\"861b129e42aac2de8166ad76ded0781c\"},\"3d134cec-2e95-4d43-bc4b-e552d382f73c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3d134cec-2e95-4d43-bc4b-e552d382f73c\",\"id\":\"030b7e2842cde1ce8a1d8e545d0e068a\"},\"3f146225-bd3e-448b-b180-034b880bd662\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3f146225-bd3e-448b-b180-034b880bd662\",\"id\":\"3d573a80fc3bd747d292d75c7751c523\"},\"3f9a284a-7485-460c-b827-9df8cd50720e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.21.Description\",\"units\":null,\"value\":\"Insitu Concrete\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3f9a284a-7485-460c-b827-9df8cd50720e\",\"id\":\"632da2ce6a52e51df22a05b776421b49\"},\"40e844db-ba22-4ddc-bc15-1fc47f5b12e7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"40e844db-ba22-4ddc-bc15-1fc47f5b12e7\",\"id\":\"a8e9d80b93cd5f965d00aaffad6786e9\"},\"444d97fc-d9b6-4424-943d-37ac498a46c4\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Item Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"444d97fc-d9b6-4424-943d-37ac498a46c4\",\"id\":\"d7cf7e867db30b45ac62757a6cc11b1b\"},\"46baf2f0-9232-4c37-aa7c-57e37fd5db17\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Product Type\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"46baf2f0-9232-4c37-aa7c-57e37fd5db17\",\"id\":\"b87714b8187aabde851b900a3755655a\"},\"4803c7b6-ded1-46b1-b5eb-ffe9ddcdc20b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4803c7b6-ded1-46b1-b5eb-ffe9ddcdc20b\",\"id\":\"9d6e8c6691db06081c2332c589ed2a31\"},\"48e76a50-9a4f-47a9-8074-79cc7fce9f14\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"48e76a50-9a4f-47a9-8074-79cc7fce9f14\",\"id\":\"38621e3cf898c0fa404d29b88cf6ea5a\"},\"4ac5fa74-7864-45a3-9d89-1ab998b7731a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4ac5fa74-7864-45a3-9d89-1ab998b7731a\",\"id\":\"34e84e5eed664e1ca94385e405141ef1\"},\"4c575161-247d-46a5-8ae2-72829f37725f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"NetArea(BaseQuantities)\",\"units\":\"m\u00b2\",\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4c575161-247d-46a5-8ae2-72829f37725f\",\"id\":\"c5741774b669ce685c5e53a704cc0320\"},\"4d293b70-da1a-4830-80a0-4f63b356ff61\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4d293b70-da1a-4830-80a0-4f63b356ff61\",\"id\":\"e629dd3ed399e1b8328aeaa2e90afae9\"},\"4dabccff-7cc0-42ff-a6db-29a28162d3f3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4dabccff-7cc0-42ff-a6db-29a28162d3f3\",\"id\":\"3aa01feade7f65bbd785f7083c04bacf\"},\"4fc2bd83-f0b1-41ba-8663-89e3d7f3e660\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4fc2bd83-f0b1-41ba-8663-89e3d7f3e660\",\"id\":\"e7639f4355cebda699431a60345609c4\"},\"50a015d9-917f-4ac5-884e-42f7f36b47b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"50a015d9-917f-4ac5-884e-42f7f36b47b1\",\"id\":\"9a71039a2e2272f527084f2096f9429b\"},\"51778754-e984-42cf-8a6d-a2226baf316f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"51778754-e984-42cf-8a6d-a2226baf316f\",\"id\":\"392005ecea2408b03939ef80fbb34a8f\"},\"5188c780-2bf1-460c-8bbf-043dcb4649eb\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5188c780-2bf1-460c-8bbf-043dcb4649eb\",\"id\":\"f38369c4acdfa96b7d45ac6187f8a183\"},\"5402c013-1b09-474f-b399-344a0e55a182\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Material\",\"units\":null,\"value\":\"PT\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5402c013-1b09-474f-b399-344a0e55a182\",\"id\":\"e8252b753c969222390cf77fc56237ef\"},\"579138d4-c882-45f1-bfd6-5ec6f8189161\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate Reo Area\",\"units\":null,\"value\":12,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"579138d4-c882-45f1-bfd6-5ec6f8189161\",\"id\":\"09ca602feb656474737d2cf84e89e26f\"},\"5d8a425f-4cff-44fe-9896-932e8e5639ef\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5d8a425f-4cff-44fe-9896-932e8e5639ef\",\"id\":\"805c08afb49a184af8649f63531be0e3\"},\"6248687a-e43d-4380-9f28-b98a14157187\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"6248687a-e43d-4380-9f28-b98a14157187\",\"id\":\"c300499e47eea08ec5bfd25acc9abae3\"},\"6cbcfae1-3598-4ddd-a606-41f3788c0362\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Managed Asset YesNo\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"6cbcfae1-3598-4ddd-a606-41f3788c0362\",\"id\":\"b9473955cae7232a6d5ba4a2c7669e7c\"},\"76daee20-cdee-48b9-bf5f-1dc46079927e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"76daee20-cdee-48b9-bf5f-1dc46079927e\",\"id\":\"90563b7e8ed0acad2128d08383966907\"},\"7949a6c6-d3e4-45bf-bad0-208f3ba33483\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Discipline Abbreviation\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7949a6c6-d3e4-45bf-bad0-208f3ba33483\",\"id\":\"e427ecb62bb5a5f69dc56b12dfd4583a\"},\"79dbaeea-7a11-4cb4-a521-bc04d1b7a25b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"79dbaeea-7a11-4cb4-a521-bc04d1b7a25b\",\"id\":\"d8a8a1f5d6315d9748e29bdd293d77a5\"},\"7a4a2609-0307-4c38-9a8c-4ffcd19a2d00\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Location\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7a4a2609-0307-4c38-9a8c-4ffcd19a2d00\",\"id\":\"a4c9b4d5ce413090459c99efad3f1832\"},\"7d013fce-228f-4f3c-aa01-db40e458cc6e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 8\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7d013fce-228f-4f3c-aa01-db40e458cc6e\",\"id\":\"07281437b9e050cd5ade4cca8a96227b\"},\"834ba6cf-7f91-49e5-ad0c-717d52a2507a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"834ba6cf-7f91-49e5-ad0c-717d52a2507a\",\"id\":\"7783908b6376a626de1c39d735b6cbfc\"},\"86d20f83-240f-44b9-8b27-6311eab2abcd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"86d20f83-240f-44b9-8b27-6311eab2abcd\",\"id\":\"006791a1d0d5566c3b892202b08943fd\"},\"8a91c179-c4cb-471a-b108-ad540b8267e3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Global Inherited Properties.Level Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8a91c179-c4cb-471a-b108-ad540b8267e3\",\"id\":\"3d3d901b52bacdd137ac67f702680f3d\"},\"8c59bf6c-99ff-455b-bdfa-aeb7861e522e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8c59bf6c-99ff-455b-bdfa-aeb7861e522e\",\"id\":\"363ec752016b710969c8b4454fd2d35f\"},\"8f645e8b-7523-4462-a0af-858ffeaf44dc\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon D\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8f645e8b-7523-4462-a0af-858ffeaf44dc\",\"id\":\"0e36907ad605957b17ba8556547d8ceb\"},\"91b7eb2f-0caf-45b0-a65d-83ce1eaca70e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"91b7eb2f-0caf-45b0-a65d-83ce1eaca70e\",\"id\":\"04140c6ff8418bd2c731affd5691de8e\"},\"93b76d01-67c3-4799-a5f3-296f97489bd3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Calc Room Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"93b76d01-67c3-4799-a5f3-296f97489bd3\",\"id\":\"4e270753bda04b81fd1c11bf1da5d852\"},\"9898cedf-179a-42e7-8cdc-c6c4212ec3e8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9898cedf-179a-42e7-8cdc-c6c4212ec3e8\",\"id\":\"7fd3f00069f6f1fcdab2ea0307661061\"},\"9a208060-4948-49b2-a1bf-1ab383705469\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9a208060-4948-49b2-a1bf-1ab383705469\",\"id\":\"75340685a08f1a15df5c7aebbc41a85a\"},\"9a49a9a9-21c9-4f52-aeab-ae4727be6e1d\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9a49a9a9-21c9-4f52-aeab-ae4727be6e1d\",\"id\":\"93f8195337085871545af0176f0ba282\"},\"9c2f84e0-2489-4a03-b4c8-eb44bb26fb0a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9c2f84e0-2489-4a03-b4c8-eb44bb26fb0a\",\"id\":\"fa24d55d23008ef6be1dbf3e8636c67e\"},\"9dd225d2-722b-4cb1-b972-babca7520f7e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"System Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9dd225d2-722b-4cb1-b972-babca7520f7e\",\"id\":\"056eec36214c733f8ecc448c27785434\"},\"9f484ce3-e8ae-4c20-b21c-0210b770935c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Tenancy Identifier\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9f484ce3-e8ae-4c20-b21c-0210b770935c\",\"id\":\"416b24b324db272078942a4420775ec0\"},\"ANALYTICAL_HEAT_TRANSFER_COEFFICIENT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Heat Transfer Coefficient (U)\",\"units\":\"W/(m\u00b2·K)\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:wattsPerSquareMeterKelvin-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_HEAT_TRANSFER_COEFFICIENT\",\"id\":\"8d290ae8d6ec3896b8dda96a83bb2d12\"},\"a5cb3364-d1f0-4ea5-a2d2-44114efbcf65\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"a5cb3364-d1f0-4ea5-a2d2-44114efbcf65\",\"id\":\"23ac4c2000599c233e5c390330e9108e\"},\"a77ddcdc-4c89-42c3-9d28-bc9e476c0fbe\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"a77ddcdc-4c89-42c3-9d28-bc9e476c0fbe\",\"id\":\"c60915bcbf4e2e070ccabc17694ee836\"},\"aa967357-e0b5-49f3-95a0-085e5d7d8951\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"aa967357-e0b5-49f3-95a0-085e5d7d8951\",\"id\":\"08650ab84cd1be1235a60fccfcb1f39e\"},\"ac9b78b9-e138-483b-9796-6214cf7a5bd8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Documentation.Home Story Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ac9b78b9-e138-483b-9796-6214cf7a5bd8\",\"id\":\"759d341196e21570a2f2d6199b5a9f2f\"},\"aeb679c1-1b82-4476-9099-7d13fd8ae3b8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"aeb679c1-1b82-4476-9099-7d13fd8ae3b8\",\"id\":\"0a235c1836cd2ce0c5fd3869b938946e\"},\"af8efc07-fec4-4419-8513-4a268c4141c8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"af8efc07-fec4-4419-8513-4a268c4141c8\",\"id\":\"181c0245de21c9820a83396914565762\"},\"afb0a36f-1fa3-4f07-9b05-f86f48b3c3f0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"afb0a36f-1fa3-4f07-9b05-f86f48b3c3f0\",\"id\":\"cd929dfb8d143639a136ff3716800dfc\"},\"b13fb213-450c-4f92-859a-05cd5779daf1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b13fb213-450c-4f92-859a-05cd5779daf1\",\"id\":\"1972d1f9e161892f2ba47ccb04f5e784\"},\"b41f116c-c6f7-418e-bf4b-61cc815f8d99\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level Number(Global Inherited Properties)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b41f116c-c6f7-418e-bf4b-61cc815f8d99\",\"id\":\"09caeb5d7565c779852e7802437af4a2\"},\"b594497d-7b5e-4221-aa1d-063f073aa326\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b594497d-7b5e-4221-aa1d-063f073aa326\",\"id\":\"ee718148bffebbcfb7e50f5f2be7f91f\"},\"b753aced-e142-45f9-9bb6-d7edce1df108\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Calc Location\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b753aced-e142-45f9-9bb6-d7edce1df108\",\"id\":\"ab32fc38096b2f538301b596be3ab123\"},\"b90ec63a-c51f-400a-bff1-66b8d0765f47\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate Reo Volume\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b90ec63a-c51f-400a-bff1-66b8d0765f47\",\"id\":\"5642967c62f928019c93b1bbc0e81c26\"},\"b958fd3c-5ea1-43a2-bc5c-df212ed8cf33\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b958fd3c-5ea1-43a2-bc5c-df212ed8cf33\",\"id\":\"ea40993e42d6379ab80b5b7d526347c2\"},\"b9d05d9c-a5f5-4a92-8811-9c5e2eefabd8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b9d05d9c-a5f5-4a92-8811-9c5e2eefabd8\",\"id\":\"c93f07ca1b6c3ac1c114474dc9aeeaca\"},\"bf3519b8-7b28-497e-97d8-afd4bf76203b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bf3519b8-7b28-497e-97d8-afd4bf76203b\",\"id\":\"3c46724925e301e2f88d4ecf4b6150f9\"},\"bfb5b2c0-aa1f-47ae-9cc9-70f7feaef0ea\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bfb5b2c0-aa1f-47ae-9cc9-70f7feaef0ea\",\"id\":\"e8a6f622ac2589ad9fadd82e76c4c10c\"},\"bfd7311c-35b9-447d-9683-8ce244f8c1ad\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Remarks\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bfd7311c-35b9-447d-9683-8ce244f8c1ad\",\"id\":\"05d34f2e81b12d25b60b1cd6a3788468\"},\"c4520aa3-cdf4-46b7-9539-180edc16d223\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c4520aa3-cdf4-46b7-9539-180edc16d223\",\"id\":\"54c0d9d278b045cbcb57fe4b2d477b86\"},\"c5b0f410-b4ee-4552-ac04-06fa0c13ec3b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Level/Floor\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c5b0f410-b4ee-4552-ac04-06fa0c13ec3b\",\"id\":\"7365bc4a64abd37f8dbed69316983d60\"},\"c67d4cb4-b2d6-426f-8d05-ac6fbe0bd267\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c67d4cb4-b2d6-426f-8d05-ac6fbe0bd267\",\"id\":\"7caf73cac38e203374836cef4827f0ae\"},\"c7ce9441-9aba-45ab-acbb-74e687481466\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.22.Number\",\"units\":null,\"value\":\"22-03 35 13\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c7ce9441-9aba-45ab-acbb-74e687481466\",\"id\":\"8b96e2130eddfb81c6a35635c5654079\"},\"cab1938b-5da8-4b53-9f0c-bb473c1966a4\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level Number(Global Inherited Properties)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"cab1938b-5da8-4b53-9f0c-bb473c1966a4\",\"id\":\"baed41d276e750b374a5ca62366d0e56\"},\"cda17719-cef4-4c69-92f1-e111e85353b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"cda17719-cef4-4c69-92f1-e111e85353b1\",\"id\":\"a00fb37b573181a2bb6070b416ac2c87\"},\"ce24f3b1-369d-42bb-987e-ac0b45c4f8da\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.23.Description\",\"units\":null,\"value\":\"Concrete Structural Floor Decks\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ce24f3b1-369d-42bb-987e-ac0b45c4f8da\",\"id\":\"57445c90784f528c2b0f414f9233a42d\"},\"ce25a030-e3d0-4856-bbc0-a1cdd8f4d4ff\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ce25a030-e3d0-4856-bbc0-a1cdd8f4d4ff\",\"id\":\"deb71e96bcf8157d78c59266aaaa86d1\"},\"d05b3c99-0643-409d-ad3b-2704d324bbcd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d05b3c99-0643-409d-ad3b-2704d324bbcd\",\"id\":\"34d4a2c23bc26a1545523ae8597223ca\"},\"d608342a-e8f5-4ec9-9626-771914eb3da2\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d608342a-e8f5-4ec9-9626-771914eb3da2\",\"id\":\"b640302338235776966b78b0735abcca\"},\"d8b20410-414f-4777-8614-a7564519c6cd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.21.Number\",\"units\":null,\"value\":\"21-02 10 10 20 04\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d8b20410-414f-4777-8614-a7564519c6cd\",\"id\":\"60e194032a186046e1a6db66376b97fb\"},\"db575143-7118-4f29-813e-2ced4535a170\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"db575143-7118-4f29-813e-2ced4535a170\",\"id\":\"29e7f431d56d49569acb66b3f0621eb5\"},\"dd0cf380-59d8-4d9f-82da-3e2be59e23a1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"dd0cf380-59d8-4d9f-82da-3e2be59e23a1\",\"id\":\"b2887224f02b48130a75948e3f924e61\"},\"dedec34c-f507-4242-a85f-07a816ff1128\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"dedec34c-f507-4242-a85f-07a816ff1128\",\"id\":\"eef57991b50ee252c88dcee0dc06fddd\"},\"e4af54de-6137-43b0-97d4-c2260a1a68c3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"e4af54de-6137-43b0-97d4-c2260a1a68c3\",\"id\":\"f00202e050fa8a6ea223906e11e870bd\"},\"ea3ba87c-ae3c-47ed-886d-754f2359389c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ea3ba87c-ae3c-47ed-886d-754f2359389c\",\"id\":\"0cd769e3fd9905a59cae6cea37f77b46\"},\"ed6b5c87-b77c-45ab-9d90-8f26e365bca1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ed6b5c87-b77c-45ab-9d90-8f26e365bca1\",\"id\":\"76cd1f61df7f11197ead15a92693abae\"},\"ee8153af-4866-45f2-a9a2-b6342ccb1dd6\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ee8153af-4866-45f2-a9a2-b6342ccb1dd6\",\"id\":\"c32a465b6682dbc0908107858c6b9b6b\"},\"ef2d5da9-0b71-4617-a78c-cf4395808169\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ef2d5da9-0b71-4617-a78c-cf4395808169\",\"id\":\"28aa3437bdf40659590c753a3e842193\"},\"f3b20cd0-059c-48a6-b744-7a9babf1cb29\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f3b20cd0-059c-48a6-b744-7a9babf1cb29\",\"id\":\"094df4c6e1d89bda1c3d32d19b859e57\"},\"f69d55e2-e23c-4a77-999d-45bae64d5856\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f69d55e2-e23c-4a77-999d-45bae64d5856\",\"id\":\"3fffe0348a0f398a26003c9552c4dbc9\"},\"f6a1fceb-536f-4261-ad25-4f1fb4dcda76\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Concrete Grade\",\"units\":null,\"value\":\"40 MPa\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f6a1fceb-536f-4261-ad25-4f1fb4dcda76\",\"id\":\"acd05a5a6632bf49f56fe8d9b32ca1ff\"},\"f7c3a959-7884-46fd-97a6-cca3cea07fe7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f7c3a959-7884-46fd-97a6-cca3cea07fe7\",\"id\":\"131c5cc690b61877eac2f73f73cd69ad\"},\"fac5d675-3756-485d-9500-4f2aa3096a38\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Product Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fac5d675-3756-485d-9500-4f2aa3096a38\",\"id\":\"b5ba47412a41ee8f0e0b6309435f08e7\"},\"fb16e643-73bd-4c8d-a506-99506b010546\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate PT Area\",\"units\":null,\"value\":4.5,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fb16e643-73bd-4c8d-a506-99506b010546\",\"id\":\"8607ae9e4c1350ec9eee23830a12c77e\"},\"fb272f85-666a-45a4-ae16-fa4d620d81b7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.23.Number\",\"units\":null,\"value\":\"23-13 35 23 11 11\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fb272f85-666a-45a4-ae16-fa4d620d81b7\",\"id\":\"5bcbae5ccbbdc5366970db0d9dd64eca\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM_SURVEY\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom Survey\",\"units\":\"mm\",\"value\":30699.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM_SURVEY\",\"id\":\"830d5e76c5b5b84bbab7f9f52e80dfef\"},\"id\":\"e24896645d6932e8d2edc7b56bcd65b2\"}" + )] + [TestCase( + "{\n \"applicationId\": null,\n \"speckle_type\": \"Base\",\n \"name\": \"Physically Based (1)\",\n \"diffuse\": -11810867,\n \"opacity\": 1,\n \"emissive\": -16777216,\n \"metalness\": 0,\n \"roughness\": 0.2,\n \"totalChildrenCount\": 0,\n \"id\": \"3ef9f1e3deb7e8057f9eceb29ff2ea88\"\n}" + )] + public void Serialize_Id_Stable(string json) + { + var jObject = JObject.Parse(json); + var id = jObject["id"].NotNull().Value(); + jObject.Remove("id"); + jObject.Remove("__closure"); + var jsonWithoutId = jObject.ToString(Formatting.None); + var newId = IdGenerator.ComputeId(jsonWithoutId); + id.ShouldBe(newId); + } + + [Test] + [TestCase("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b")] + public async Task Roundtrip_Test_Old(string fileName, string rootId) + { + var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); + var json = await ReadJson(fullName); + var closure = ReadAsObjects(json); + var deserializer = new SpeckleObjectDeserializer + { + ReadTransport = new TestTransport(closure), + CancellationToken = default, + }; + + var writtenObjects = new Dictionary(); + var writeTransport = new TestTransport(writtenObjects); + var serializer = new SpeckleObjectSerializer([writeTransport]); + var newIds = new Dictionary(); + var oldIds = new Dictionary(); + var idToBase = new Dictionary(); + foreach (var (id, objJson) in closure) + { + var base1 = await deserializer.DeserializeAsync(objJson); + base1.id.ShouldBe(id); + var j = serializer.Serialize(base1); + //j.ShouldBe(objJson); + JToken.DeepEquals(JObject.Parse(j), JObject.Parse(objJson)); + newIds.Add(base1.id, j); + oldIds.Add(id, j); + idToBase.Add(id, base1); + } + } + + [Test] + [TestCase("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 4610)] + public async Task Roundtrip_Test_New(string fileName, string rootId, int count) + { + var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); + var json = await ReadJson(fullName); + var closure = ReadAsObjects(json); + + var o = new ObjectLoader( + new DummySqLiteReceiveManager(closure), + new DummyReceiveServerObjectManager(closure), + string.Empty, + null + ); + var process = new DeserializeProcess(null, o); + var root = await process.Deserialize(rootId, default, new DeserializeOptions(true)); + + var newIdToJson = new ConcurrentDictionary(); + var serializeProcess = new SerializeProcess( + null, + new DummySqLiteSendManager(), + new DummySendServerObjectManager(newIdToJson), + new SpeckleBaseChildFinder(new SpeckleBasePropertyGatherer()), + new SpeckleBasePropertyGatherer() + ); + var (rootId2, _) = await serializeProcess.Serialize( + string.Empty, + root, + default, + new SerializeProcessOptions(true, false) + ); + + rootId2.ShouldBe(root.id); + newIdToJson.Count.ShouldBe(count); + + foreach (var newKvp in newIdToJson) + { + if (closure.TryGetValue(newKvp.Key, out var newValue)) + { + JToken.DeepEquals(JObject.Parse(newValue), JObject.Parse(newKvp.Value)); + } + } + } } diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs index 5b1048d5..194c59ee 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs @@ -46,7 +46,7 @@ public class GeneralDeserializer : IDisposable [Benchmark] public async Task RunTest_New() { - var sqlite = new SQLiteCacheManager(streamId); + var sqlite = new SQLiteReceiveCacheManager(streamId); var serverObjects = new ServerObjectManager( TestDataHelper.ServiceProvider.GetRequiredService(), TestDataHelper.ServiceProvider.GetRequiredService(), diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs new file mode 100644 index 00000000..6e323e66 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs @@ -0,0 +1,84 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using Microsoft.Extensions.DependencyInjection; +using Speckle.Objects.Geometry; +using Speckle.Sdk.Api; +using Speckle.Sdk.Api.GraphQL.Enums; +using Speckle.Sdk.Api.GraphQL.Models; +using Speckle.Sdk.Common; +using Speckle.Sdk.Credentials; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation; +using Speckle.Sdk.Transports; + +namespace Speckle.Sdk.Tests.Performance.Benchmarks; + +/// +/// How many threads on our Deserializer is optimal +/// +[MemoryDiagnoser] +[SimpleJob(RunStrategy.Monitoring, iterationCount: 1)] +public class GeneralSendTest +{ + private Base _testData; + private IOperations _operations; + private ServerTransport _remote; + private Account acc; + private Client client; + + private Project _project; + + [GlobalSetup] + public async Task Setup() + { + TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); + using var dataSource = new TestDataHelper(); + await dataSource + .SeedTransport( + new Account() { serverInfo = new() { url = "https://latest.speckle.systems/" } }, + "2099ac4b5f", + "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6", + false + ) + .ConfigureAwait(false); + + SpeckleObjectDeserializer deserializer = new() { ReadTransport = dataSource.Transport }; + string data = await dataSource.Transport.GetObject(dataSource.ObjectId).NotNull(); + _testData = await deserializer.DeserializeAsync(data).NotNull(); + _operations = TestDataHelper.ServiceProvider.GetRequiredService(); + + acc = TestDataHelper + .ServiceProvider.GetRequiredService() + .GetAccounts("https://latest.speckle.systems") + .First(); + + client = TestDataHelper.ServiceProvider.GetRequiredService().Create(acc); + + _project = await client.Project.Create( + new($"General Send Test run {Guid.NewGuid()}", null, ProjectVisibility.Public) + ); + _remote = TestDataHelper.ServiceProvider.GetRequiredService().Create(acc, _project.id); + } + + [Benchmark(Baseline = true)] + public async Task Send_old() + { + using SQLiteTransport local = new(); + var res = await _operations.Send(_testData, [_remote, local]); + return await TagVersion($"Send_old {Guid.NewGuid()}", res.rootObjId); + } + + [Benchmark] + public async Task Send_new() + { + var res = await _operations.Send2(new(acc.serverInfo.url), _project.id, acc.token, _testData); + return await TagVersion($"Send_new {Guid.NewGuid()}", res.rootObjId); + } + + private async Task TagVersion(string name, string objectId) + { + var model = await client.Model.Create(new(name, null, _project.id)); + return await client.Version.Create(new(objectId, model.id, _project.id)); + } +} diff --git a/tests/Speckle.Sdk.Tests.Performance/Program.cs b/tests/Speckle.Sdk.Tests.Performance/Program.cs index 033015f1..889b29d8 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Program.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Program.cs @@ -1,5 +1,9 @@ // See https://aka.ms/new-console-template for more information using BenchmarkDotNet.Running; +using Speckle.Sdk.Tests.Performance.Benchmarks; BenchmarkSwitcher.FromAssemblies([typeof(Program).Assembly]).Run(args); +// var sut = new GeneralSendTest(); +// await sut.Setup(); +// await sut.Send2(); diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs index 1a8e273d..c08e9ee5 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs @@ -203,17 +203,7 @@ public sealed class SendReceiveLocal : IDisposable ); } - ProgressArgs? progress = null; - (_commitId02, _) = await _operations.Send( - myObject, - _sut, - false, - onProgressAction: new UnitTestProgress(x => - { - progress = x; - }) - ); - progress.ShouldNotBeNull(); + (_commitId02, _) = await _operations.Send(myObject, _sut, false); } [Test(Description = "Should show progress!"), Order(5)] diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs index 5eed8698..36190b59 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs @@ -1,7 +1,16 @@ +using System.Collections.Concurrent; +using System.Text; using NUnit.Framework; +using Shouldly; +using Speckle.Newtonsoft.Json.Linq; using Speckle.Sdk.Common; +using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Host; using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation; +using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Serialisation.V2.Send; +using Speckle.Sdk.Transports; namespace Speckle.Sdk.Tests.Unit.Models; diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs index fba84247..77689518 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs @@ -93,19 +93,6 @@ public abstract class TransportTests } } - [Test] - public async Task ProgressAction_Called_OnSaveObject() - { - bool wasCalled = false; - Sut.NotNull().OnProgressAction = new UnitTestProgress(_ => wasCalled = true); - - Sut.SaveObject("12345", "fake payload data"); - - await Sut.WriteComplete(); - - Assert.That(wasCalled, Is.True); - } - [Test] public void ToString_IsNotEmpty() {