Merge branch 'dev' into adam/cnx-1367-build-subset-when-tagged
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled

This commit is contained in:
Adam Hathcock
2025-03-10 09:50:54 +00:00
21 changed files with 601 additions and 118 deletions
@@ -108,6 +108,12 @@ public abstract class AutocadReceiveBaseBinding : IReceiveBinding
{
// reenable document activation
Application.DocumentManager.DocumentActivationEnabled = true;
// regenerate doc to flush graphics, sometimes some objects (ellipses, nurbs curves) do not appear fully visible after receive.
// Adding a regen (must be run on main thread) here, but it doesn't seem to work:
// it's run on main thread, tried sending the "regen" string to execute, also tried regen after every object bake, but still can't fix.
// the objects should appear visible if you manually call the "regen" command after the operation finishes, or click on a view on the view cube which also calls regen.
Application.DocumentManager.CurrentDocument.Editor.Regen();
}
}
}
@@ -83,7 +83,7 @@ public class AutocadHostObjectBuilder(
colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
}
// 5 - Convert atomic objects
// 4 - Convert atomic objects
HashSet<ReceiveConversionResult> results = new();
HashSet<string> bakedObjectIds = new();
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
@@ -116,7 +116,7 @@ public class AutocadHostObjectBuilder(
}
}
// 6 - Convert instances
// 5 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
@@ -129,7 +129,7 @@ public class AutocadHostObjectBuilder(
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.UnionWith(instanceConversionResults);
// 7 - Create groups
// 6 - Create groups
if (unpackedRoot.GroupProxies != null)
{
IReadOnlyCollection<ReceiveConversionResult> groupResults = groupBaker.CreateGroups(
@@ -5,10 +5,15 @@ using Speckle.Sdk.Common.Exceptions;
namespace Speckle.Converters.Rhino.ToHost.Raw;
public interface IFlatPointListToHostConverter : ITypedConverter<IReadOnlyList<double>, Point3dList>
{
IEnumerable<RG.Point3d> ConvertToEnum(IReadOnlyList<double> target);
}
/// <summary>
/// Converts a flat list of raw double values to a Point3dList.
/// </summary>
public class FlatPointListToHostConverter : ITypedConverter<IReadOnlyList<double>, Point3dList>
public class FlatPointListToHostConverter : IFlatPointListToHostConverter
{
/// <summary>
/// Converts a flat list of raw double values to a Point3dList.
@@ -20,20 +25,19 @@ public class FlatPointListToHostConverter : ITypedConverter<IReadOnlyList<double
/// with the numbers being coordinates of each point in the format {x1, y1, z1, x2, y2, z2, ..., xN, yN, zN}
/// </remarks>
/// <exception cref="SpeckleException">Throws when the input list count is not a multiple of 3.</exception>
public Point3dList Convert(IReadOnlyList<double> target)
public Point3dList Convert(IReadOnlyList<double> target) => new(ConvertToEnum(target));
//avoids temporary collection by using this when necessary
public IEnumerable<RG.Point3d> ConvertToEnum(IReadOnlyList<double> target)
{
if (target.Count % 3 != 0)
{
throw new ValidationException("Array malformed: length%3 != 0.");
}
var points = new List<RG.Point3d>(target.Count / 3);
for (int i = 2; i < target.Count; i += 3)
{
points.Add(new RG.Point3d(target[i - 2], target[i - 1], target[i]));
yield return new RG.Point3d(target[i - 2], target[i - 1], target[i]);
}
return new Point3dList(points);
}
}
@@ -1,20 +1,12 @@
using System.Drawing;
using Rhino.Collections;
using System.Drawing;
using Speckle.Converters.Common.Objects;
using Speckle.Objects.Utils;
using Speckle.Sdk;
namespace Speckle.Converters.Rhino.ToHost.Raw;
public class MeshToHostConverter : ITypedConverter<SOG.Mesh, RG.Mesh>
public class MeshToHostConverter(IFlatPointListToHostConverter pointListConverter) : ITypedConverter<SOG.Mesh, RG.Mesh>
{
private readonly ITypedConverter<IReadOnlyList<double>, Point3dList> _pointListConverter;
public MeshToHostConverter(ITypedConverter<IReadOnlyList<double>, Point3dList> pointListConverter)
{
_pointListConverter = pointListConverter;
}
/// <summary>
/// Converts a Speckle mesh object to a Rhino mesh object.
/// </summary>
@@ -25,26 +17,35 @@ public class MeshToHostConverter : ITypedConverter<SOG.Mesh, RG.Mesh>
{
RG.Mesh m = new();
var vertices = _pointListConverter.Convert(target.vertices);
var colors = ConvertVertexColors(target.colors);
var vertexNormals = ConvertVertexNormals(target.vertexNormals);
var textureCoordinates = ConvertTextureCoordinates(target.textureCoordinates);
var vertices = pointListConverter.ConvertToEnum(target.vertices);
m.Vertices.AddVertices(vertices);
if (colors.Length != 0 && !m.VertexColors.SetColors(colors))
if (target.colors.Count != 0)
{
throw new SpeckleException("Failed to set Vertex Colors");
var colors = ConvertVertexColors(target.colors);
if (!m.VertexColors.SetColors(colors))
{
throw new SpeckleException("Failed to set Vertex Colors");
}
}
if (vertexNormals.Length != 0 && !m.Normals.SetNormals(vertexNormals))
if (target.vertexNormals.Count != 0)
{
throw new SpeckleException("Failed to set Vertex Normals");
var vertexNormals = ConvertVertexNormals(target.vertexNormals);
if (!m.Normals.SetNormals(vertexNormals))
{
throw new SpeckleException("Failed to set Vertex Normals");
}
}
if (textureCoordinates.Length != 0 && !m.TextureCoordinates.SetTextureCoordinates(textureCoordinates))
if (target.textureCoordinates.Count != 0)
{
throw new SpeckleException("Failed to set Texture Coordinates");
var textureCoordinates = ConvertTextureCoordinates(target.textureCoordinates);
if (!m.TextureCoordinates.SetTextureCoordinates(textureCoordinates))
{
throw new SpeckleException("Failed to set Texture Coordinates");
}
}
AssignMeshFaces(target, m);
@@ -60,6 +61,7 @@ public class MeshToHostConverter : ITypedConverter<SOG.Mesh, RG.Mesh>
while (i < target.faces.Count)
{
int n = target.faces[i];
// For backwards compatibility. Old meshes will have "0" for triangle face, "1" for quad face.
// Newer meshes have "3" for triangle face, "4" for quad" face and "5...n" for n-gon face.
if (n < 3)
@@ -67,32 +69,25 @@ public class MeshToHostConverter : ITypedConverter<SOG.Mesh, RG.Mesh>
n += 3; // 0 -> 3, 1 -> 4
}
if (n == 3)
switch (n)
{
// triangle
m.Faces.AddFace(new RG.MeshFace(target.faces[i + 1], target.faces[i + 2], target.faces[i + 3]));
}
else if (n == 4)
{
// quad
m.Faces.AddFace(
new RG.MeshFace(target.faces[i + 1], target.faces[i + 2], target.faces[i + 3], target.faces[i + 4])
);
}
else
{
// n-gon
var triangles = MeshTriangulationHelper.TriangulateFace(i, target, false);
var faceIndices = new List<int>(triangles.Count);
for (int t = 0; t < triangles.Count; t += 3)
case 3:
// triangle
m.Faces.AddFace(target.faces[i + 1], target.faces[i + 2], target.faces[i + 3]);
break;
case 4:
// quad
m.Faces.AddFace(target.faces[i + 1], target.faces[i + 2], target.faces[i + 3], target.faces[i + 4]);
break;
default:
{
var face = new RG.MeshFace(triangles[t], triangles[t + 1], triangles[t + 2]);
faceIndices.Add(m.Faces.AddFace(face));
// n-gon
var faceIndices = GetNgonFaceIndices(target, i, m).ToList();
var vertexIndices = GetNgonVertexIndices(target.faces, i, n).ToList();
RG.MeshNgon ngon = RG.MeshNgon.Create(vertexIndices, faceIndices);
m.Ngons.AddNgon(ngon);
break;
}
RG.MeshNgon ngon = RG.MeshNgon.Create(target.faces.GetRange(i + 1, n), faceIndices);
m.Ngons.AddNgon(ngon);
}
i += n + 1;
@@ -102,6 +97,24 @@ public class MeshToHostConverter : ITypedConverter<SOG.Mesh, RG.Mesh>
m.Faces.CullDegenerateFaces();
}
private static IEnumerable<int> GetNgonFaceIndices(SOG.Mesh target, int start, RG.Mesh m)
{
var triangles = MeshTriangulationHelper.TriangulateFace(start, target, false);
for (int t = 0; t < triangles.Count; t += 3)
{
int faceIndex = m.Faces.AddFace(triangles[t], triangles[t + 1], triangles[t + 2]);
yield return faceIndex;
}
}
private static IEnumerable<int> GetNgonVertexIndices(List<int> faces, int start, int vertexCount)
{
for (int n = 0; n < vertexCount; n++)
{
yield return faces[start + 1 + n];
}
}
private static RG.Point2f[] ConvertTextureCoordinates(IReadOnlyList<double> textureCoordinates)
{
var converted = new RG.Point2f[textureCoordinates.Count / 2];
@@ -32,7 +32,6 @@ public sealed class BrowserBridge : IBrowserBridge
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
private readonly IThreadOptions _threadOptions;
private readonly IBrowserScriptExecutor _browserScriptExecutor;
private readonly IJsonSerializer _jsonSerializer;
@@ -64,16 +63,13 @@ public sealed class BrowserBridge : IBrowserBridge
IJsonSerializer jsonSerializer,
ILogger<BrowserBridge> logger,
IBrowserScriptExecutor browserScriptExecutor,
IThreadOptions threadOptions,
ITopLevelExceptionHandler topLevelExceptionHandler
)
{
_threadContext = threadContext;
_jsonSerializer = jsonSerializer;
_logger = logger;
// Capture the main thread's SynchronizationContext
_browserScriptExecutor = browserScriptExecutor;
_threadOptions = threadOptions;
_topLevelExceptionHandler = topLevelExceptionHandler;
}
@@ -42,8 +42,11 @@ public abstract class DocumentModelStore(IJsonSerializer serializer)
// In theory this should never really happen, but if it does
public ModelCard GetModelById(string id)
{
var model = _models.First(model => model.ModelCardId == id) ?? throw new ModelNotFoundException();
return model;
lock (_models)
{
var model = _models.FirstOrDefault(model => model.ModelCardId == id) ?? throw new ModelNotFoundException();
return model;
}
}
public void AddModel(ModelCard model)
@@ -45,7 +45,7 @@ public static class Import
public static void AddIFCImporter(this ServiceCollection serviceCollection)
{
serviceCollection.AddSpeckleSdk(HostApplications.Other, HostAppVersion.v2024, "IFC-Importer");
serviceCollection.AddSpeckleSdk(new("IFC", "ifc"), HostAppVersion.v2024, "IFC-Importer");
serviceCollection.AddSpeckleWebIfc();
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
}
@@ -103,7 +103,7 @@ public static class Import
// 8 - Create the version (commit)
using var apiClient = clientFactory.Create(account);
var commit = await apiClient.Version.Create(
new CreateVersionInput(rootId, modelId, streamId, message: commitMessage)
new CreateVersionInput(rootId, modelId, streamId, message: commitMessage, sourceApplication: "IFC")
);
ms = ms2;
ms2 = stopwatch.ElapsedMilliseconds;
@@ -2,7 +2,7 @@
using NUnit.Framework;
using Speckle.Connectors.Common.Cancellation;
namespace Speckle.Connectors.Common.Tests;
namespace Speckle.Connectors.Common.Tests.Cancellation;
public class CancellationManagerTests
{
@@ -0,0 +1,131 @@
using System.Reflection;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Testing;
namespace Speckle.Connectors.Common.Tests.Operations;
public class SendOperationTests : MoqTest
{
#pragma warning disable CA1034
[SpeckleType("TestBase")]
public class TestBase : Base;
#pragma warning restore CA1034
[Test]
#pragma warning disable CA1506
public async Task Execute()
#pragma warning restore CA1506
{
TypeLoader.Reset();
TypeLoader.Initialize(Assembly.GetExecutingAssembly());
var rootObjectBuilder = Create<IRootObjectBuilder<object>>();
var sendConversionCache = Create<ISendConversionCache>();
var accountService = Create<IAccountService>();
var sendProgress = Create<ISendProgress>();
var operations = Create<IOperations>();
var sendOperationVersionRecorder = Create<ISendOperationVersionRecorder>();
var activityFactory = Create<ISdkActivityFactory>();
var threadContext = Create<IThreadContext>();
var ct = new CancellationToken();
var objects = new List<object>();
var sendInfo = new SendInfo(string.Empty, new Uri("https://localhost"), string.Empty, string.Empty, string.Empty);
var progress = Create<IProgress<CardProgress>>();
var conversionResults = new List<SendConversionResult>();
var rootResult = new RootObjectBuilderResult(new TestBase(), conversionResults);
rootObjectBuilder.Setup(x => x.Build(objects, sendInfo, progress.Object, ct)).ReturnsAsync(rootResult);
var rootId = "rootId";
var refs = new Dictionary<Id, ObjectReference>();
var serializeProcessResults = new SerializeProcessResults(rootId, refs);
threadContext
.Setup(x => x.RunOnThreadAsync(It.IsAny<Func<Task<SerializeProcessResults>>>(), false))
.ReturnsAsync(serializeProcessResults);
var sendOperation = new SendOperation<object>(
rootObjectBuilder.Object,
sendConversionCache.Object,
accountService.Object,
sendProgress.Object,
operations.Object,
sendOperationVersionRecorder.Object,
activityFactory.Object,
threadContext.Object
);
var result = await sendOperation.Execute(objects, sendInfo, progress.Object, ct);
result.Should().NotBeNull();
rootResult.RootObject["version"].Should().Be(3);
result.RootObjId.Should().Be(rootId);
result.ConvertedReferences.Should().BeSameAs(refs);
result.ConversionResults.Should().BeSameAs(conversionResults);
}
[Test]
public async Task Send()
{
TypeLoader.Reset();
TypeLoader.Initialize(Assembly.GetExecutingAssembly());
var rootObjectBuilder = Create<IRootObjectBuilder<object>>();
var sendConversionCache = Create<ISendConversionCache>();
var accountService = Create<IAccountService>();
var sendProgress = Create<ISendProgress>();
var operations = Create<IOperations>();
var sendOperationVersionRecorder = Create<ISendOperationVersionRecorder>();
var activityFactory = Create<ISdkActivityFactory>();
var threadContext = Create<IThreadContext>();
var commitObject = new TestBase();
var projectId = "projectId";
var modelId = "modelId";
var accountId = "accountId";
var url = new Uri("https://localhost");
var sendInfo = new SendInfo(accountId, url, projectId, modelId, string.Empty);
var progress = Create<IProgress<CardProgress>>(MockBehavior.Loose);
var ct = new CancellationToken();
var token = "token";
var account = new Account() { token = token };
var rootId = "rootId";
var refs = new Dictionary<Id, ObjectReference>();
var serializeProcessResults = new SerializeProcessResults(rootId, refs);
accountService.Setup(x => x.GetAccountWithServerUrlFallback(accountId, url)).Returns(account);
activityFactory.Setup(x => x.Start("SendOperation", "Send")).Returns((ISdkActivity?)null);
operations
.Setup(x => x.Send2(url, projectId, token, commitObject, It.IsAny<PassthroughProgress>(), ct))
.ReturnsAsync(serializeProcessResults);
sendConversionCache.Setup(x => x.StoreSendResult(projectId, refs));
sendProgress.Setup(x => x.Begin());
sendOperationVersionRecorder.Setup(x => x.RecordVersion(rootId, sendInfo, account, ct)).Returns(Task.CompletedTask);
var sendOperation = new SendOperation<object>(
rootObjectBuilder.Object,
sendConversionCache.Object,
accountService.Object,
sendProgress.Object,
operations.Object,
sendOperationVersionRecorder.Object,
activityFactory.Object,
threadContext.Object
);
var result = await sendOperation.Send(commitObject, sendInfo, progress.Object, ct);
result.Should().Be(serializeProcessResults);
}
}
@@ -11,13 +11,12 @@
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit.Analyzers" />
<PackageReference Include="NUnit3TestAdapter" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
<ProjectReference Include="..\Speckle.Testing\Speckle.Testing.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,18 @@
using System.Diagnostics.CodeAnalysis;
namespace Speckle.Connectors.Common.Tests.Threading;
[SuppressMessage("Design", "CA1008:Enums should have zero value")]
public enum Funcs
{
RunMain,
RunWorker,
RunMainAsync,
RunWorkerAsync,
WorkerToMainAsync,
MainToWorker,
WorkerToMain,
MainToWorkerAsync,
RunMainAsync_T,
RunWorkerAsync_T
}
@@ -0,0 +1,103 @@
using FluentAssertions;
using Speckle.Connectors.Common.Threading;
namespace Speckle.Connectors.Common.Tests.Threading;
public class TestThreadContext(bool isMain) : ThreadContext
{
public override bool IsMainThread => isMain;
public Funcs? Func { get; set; }
protected override void RunMain(Action action)
{
action();
Func.Should().BeNull();
Func = Funcs.RunMain;
}
protected override void RunWorker(Action action)
{
action();
Func.Should().BeNull();
Func = Funcs.RunWorker;
}
protected override async Task<T> WorkerToMainAsync<T>(Func<Task<T>> action)
{
var x = await action();
Func.Should().BeNull();
Func = Funcs.WorkerToMainAsync;
return x;
}
protected override async Task<T> MainToWorkerAsync<T>(Func<Task<T>> action)
{
var x = await action();
Func.Should().BeNull();
Func = Funcs.MainToWorkerAsync;
return x;
}
protected override Task<T> WorkerToMain<T>(Func<T> action)
{
var x = action();
Func.Should().BeNull();
Func = Funcs.WorkerToMain;
return Task.FromResult(x);
}
protected override Task<T> MainToWorker<T>(Func<T> action)
{
var x = action();
Func.Should().BeNull();
Func = Funcs.MainToWorker;
return Task.FromResult(x);
}
protected override Task<T> RunMainAsync<T>(Func<T> action)
{
var x = action();
Func.Should().BeNull();
Func = Funcs.RunMainAsync_T;
return Task.FromResult(x);
}
protected override Task<T> RunWorkerAsync<T>(Func<T> action)
{
var x = action();
Func.Should().BeNull();
Func = Funcs.RunWorkerAsync_T;
return Task.FromResult(x);
}
protected override async Task RunMainAsync(Func<Task> action)
{
await action();
Func.Should().BeNull();
Func = Funcs.RunMainAsync;
}
protected override async Task RunWorkerAsync(Func<Task> action)
{
await action();
Func.Should().BeNull();
Func = Funcs.RunWorkerAsync;
}
protected override async Task<T> RunMainAsync<T>(Func<Task<T>> action)
{
var x = await action();
Func.Should().BeNull();
Func = Funcs.RunMainAsync_T;
return x;
}
protected override async Task<T> RunWorkerAsync<T>(Func<Task<T>> action)
{
var x = await action();
Func.Should().BeNull();
Func = Funcs.RunWorkerAsync_T;
return x;
}
}
@@ -0,0 +1,93 @@
using FluentAssertions;
using Moq;
using NUnit.Framework;
using Speckle.Connectors.Common.Threading;
using Speckle.Testing;
namespace Speckle.Connectors.Common.Tests.Threading;
public class ThreadContextExtensionTests : MoqTest
{
[Test]
public async Task RunOnMain()
{
Action a = () => { };
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThread(a, true)).Returns(Task.CompletedTask);
await tc.Object.RunOnMain(a);
}
[Test]
public async Task RunOnWorker()
{
Action a = () => { };
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThread(a, false)).Returns(Task.CompletedTask);
await tc.Object.RunOnWorker(a);
}
[Test]
public async Task RunOnMain_T()
{
Func<bool> a = () => true;
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThread(a, true)).ReturnsAsync(true);
(await tc.Object.RunOnMain(a)).Should().BeTrue();
}
[Test]
public async Task RunOnWorker_T()
{
Func<bool> a = () => true;
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThread(a, false)).ReturnsAsync(true);
(await tc.Object.RunOnWorker(a)).Should().BeTrue();
}
[Test]
public async Task RunOnMainAsync()
{
Func<Task> a = () => Task.CompletedTask;
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThreadAsync(a, true)).Returns(Task.CompletedTask);
await tc.Object.RunOnMainAsync(a);
}
[Test]
public async Task RunOnWorkerAsync()
{
Func<Task> a = () => Task.CompletedTask;
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThreadAsync(a, false)).Returns(Task.CompletedTask);
await tc.Object.RunOnWorkerAsync(a);
}
[Test]
public async Task RunOnMainAsync_T()
{
Func<Task<bool>> a = () => Task.FromResult(true);
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThreadAsync(a, true)).ReturnsAsync(true);
(await tc.Object.RunOnMainAsync(a)).Should().BeTrue();
}
[Test]
public async Task RunOnWorkerAsync_T()
{
Func<Task<bool>> a = () => Task.FromResult(true);
var tc = Create<IThreadContext>();
tc.Setup(x => x.RunOnThreadAsync(a, false)).ReturnsAsync(true);
await tc.Object.RunOnWorkerAsync(a);
(await tc.Object.RunOnWorkerAsync(a)).Should().BeTrue();
}
[Test]
#pragma warning disable CA1030
public async Task FireAndForget()
#pragma warning restore CA1030
{
//kind of does nothing, just making sure there's no error
Task.CompletedTask.FireAndForget();
await Task.Delay(500);
}
}
@@ -0,0 +1,93 @@
using FluentAssertions;
using NUnit.Framework;
using Speckle.Testing;
namespace Speckle.Connectors.Common.Tests.Threading;
public class ThreadContextTests : MoqTest
{
[Test]
[TestCase(true, true, Funcs.RunMain)]
[TestCase(true, false, Funcs.MainToWorker)]
[TestCase(false, true, Funcs.WorkerToMain)]
[TestCase(false, false, Funcs.RunWorker)]
public async Task RunOnThread(bool isMain, bool useMain, Funcs? result)
{
var tc = new TestThreadContext(isMain);
bool resultRan = false;
await tc.RunOnThread(
() =>
{
resultRan = true;
},
useMain
);
resultRan.Should().BeTrue();
tc.Func.Should().Be(result);
}
[Test]
[TestCase(true, true, Funcs.RunMainAsync_T)]
[TestCase(true, false, Funcs.MainToWorker)]
[TestCase(false, true, Funcs.WorkerToMain)]
[TestCase(false, false, Funcs.RunWorkerAsync_T)]
public async Task RunOnThread_T(bool isMain, bool useMain, Funcs? result)
{
var tc = new TestThreadContext(isMain);
bool resultRan = false;
var x = await tc.RunOnThread(
() =>
{
resultRan = true;
return false;
},
useMain
);
resultRan.Should().BeTrue();
x.Should().BeFalse();
tc.Func.Should().Be(result);
}
[Test]
[TestCase(true, true, Funcs.RunMainAsync)]
[TestCase(true, false, Funcs.MainToWorkerAsync)]
[TestCase(false, true, Funcs.WorkerToMainAsync)]
[TestCase(false, false, Funcs.RunWorkerAsync)]
public async Task RunOnThreadAsync(bool isMain, bool useMain, Funcs? result)
{
var tc = new TestThreadContext(isMain);
bool resultRan = false;
await tc.RunOnThreadAsync(
() =>
{
resultRan = true;
return Task.CompletedTask;
},
useMain
);
resultRan.Should().BeTrue();
tc.Func.Should().Be(result);
}
[Test]
[TestCase(true, true, Funcs.RunMainAsync_T)]
[TestCase(true, false, Funcs.MainToWorkerAsync)]
[TestCase(false, true, Funcs.WorkerToMainAsync)]
[TestCase(false, false, Funcs.RunWorkerAsync_T)]
public async Task RunOnThreadAsync_T(bool isMain, bool useMain, Funcs? result)
{
var tc = new TestThreadContext(isMain);
bool resultRan = false;
var x = await tc.RunOnThreadAsync(
() =>
{
resultRan = true;
return Task.FromResult(false);
},
useMain
);
resultRan.Should().BeTrue();
x.Should().BeFalse();
tc.Func.Should().Be(result);
}
}
@@ -55,21 +55,6 @@
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Moq": {
"type": "Direct",
"requested": "[4.20.70, )",
"resolved": "4.20.70",
"contentHash": "4rNnAwdpXJBuxqrOCzCyICXHSImOTRktCgCWXWykuF1qwoIsVvEnR7PjbMk/eLOxWvhmj5Kwt+kDV3RGUYcNwg==",
"dependencies": {
"Castle.Core": "5.1.1"
}
},
"NUnit": {
"type": "Direct",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "MT/DpAhjtiytzhTgTqIhBuWx4y26PKfDepYUHUM+5uv4TsryHC2jwFo5e6NhWkApCm/G6kZ80dRjdJFuAxq3rg=="
},
"NUnit.Analyzers": {
"type": "Direct",
"requested": "[4.2.0, )",
@@ -336,6 +321,13 @@
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.testing": {
"type": "Project",
"dependencies": {
"Moq": "[4.20.70, )",
"NUnit": "[4.1.0, )"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -354,6 +346,21 @@
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Moq": {
"type": "CentralTransitive",
"requested": "[4.20.70, )",
"resolved": "4.20.70",
"contentHash": "4rNnAwdpXJBuxqrOCzCyICXHSImOTRktCgCWXWykuF1qwoIsVvEnR7PjbMk/eLOxWvhmj5Kwt+kDV3RGUYcNwg==",
"dependencies": {
"Castle.Core": "5.1.1"
}
},
"NUnit": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "MT/DpAhjtiytzhTgTqIhBuWx4y26PKfDepYUHUM+5uv4TsryHC2jwFo5e6NhWkApCm/G6kZ80dRjdJFuAxq3rg=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
@@ -1,4 +1,5 @@
using Speckle.Sdk.Credentials;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.Common.Operations;
@@ -7,7 +8,8 @@ namespace Speckle.Connectors.Common.Operations;
/// Note: Be sure it is registered on refactorings. Otherwise, we won't be able to do any send/receive ops.
/// This can safely be registered as singleton.
/// </summary>
public class AccountService(IAccountManager accountManager)
[GenerateAutoInterface]
public class AccountService(IAccountManager accountManager) : IAccountService
{
/// <summary>
/// Account to retrieve with its id, if not exist try to retrieve from matching serverUrl.
@@ -4,7 +4,6 @@ using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.Logging;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
@@ -16,10 +15,10 @@ namespace Speckle.Connectors.Common.Operations;
public sealed class SendOperation<T>(
IRootObjectBuilder<T> rootObjectBuilder,
ISendConversionCache sendConversionCache,
AccountService accountService,
IAccountService accountService,
ISendProgress sendProgress,
IOperations operations,
IClientFactory clientFactory,
ISendOperationVersionRecorder sendOperationVersionRecorder,
ISdkActivityFactory activityFactory,
IThreadContext threadContext
)
@@ -49,7 +48,7 @@ public sealed class SendOperation<T>(
return new(rootObjId, convertedReferences, buildResult.ConversionResults);
}
private async Task<SerializeProcessResults> Send(
public async Task<SerializeProcessResults> Send(
Base commitObject,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
@@ -81,18 +80,7 @@ public sealed class SendOperation<T>(
onOperationProgressed.Report(new("Linking version to model...", null));
// 8 - Create the version (commit)
using var apiClient = clientFactory.Create(account);
_ = await apiClient
.Version.Create(
new CreateVersionInput(
sendResult.RootId,
sendInfo.ModelId,
sendInfo.ProjectId,
sourceApplication: sendInfo.SourceApplication
),
ct
)
.ConfigureAwait(true);
await sendOperationVersionRecorder.RecordVersion(sendResult.RootId, sendInfo, account, ct);
return sendResult;
}
@@ -0,0 +1,27 @@
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.Common.Operations;
[GenerateAutoInterface]
//this is unnecessary if IClientFactory.Create returned an interface
public class SendOperationVersionRecorder(IClientFactory clientFactory) : ISendOperationVersionRecorder
{
public async Task RecordVersion(string rootId, SendInfo sendInfo, Account account, CancellationToken ct)
{
using var apiClient = clientFactory.Create(account);
_ = await apiClient
.Version.Create(
new CreateVersionInput(
rootId,
sendInfo.ModelId,
sendInfo.ProjectId,
sourceApplication: sendInfo.SourceApplication
),
ct
)
.ConfigureAwait(true);
}
}
@@ -1,5 +1,8 @@
using System.Diagnostics.CodeAnalysis;
namespace Speckle.Connectors.Common.Threading;
[ExcludeFromCodeCoverage]
public class DefaultThreadContext : ThreadContext
{
//should be always newed up on the host app's main thread
@@ -6,7 +6,7 @@ namespace Speckle.Connectors.Common.Threading;
public abstract class ThreadContext : IThreadContext
{
private static readonly Task<object?> s_empty = Task.FromResult<object?>(null);
public static bool IsMainThread => Environment.CurrentManagedThreadId == 1;
public virtual bool IsMainThread => Environment.CurrentManagedThreadId == 1;
public async Task RunOnThread(Action action, bool useMain)
{
@@ -18,7 +18,7 @@ public abstract class ThreadContext : IThreadContext
}
else
{
await WorkerToMainAsync(() =>
await WorkerToMain(() =>
{
action();
return s_empty;
@@ -29,7 +29,7 @@ public abstract class ThreadContext : IThreadContext
{
if (IsMainThread)
{
await MainToWorkerAsync(() =>
await MainToWorker(() =>
{
action();
return s_empty;
@@ -37,12 +37,12 @@ public abstract class ThreadContext : IThreadContext
}
else
{
RunMain(action);
RunWorker(action);
}
}
}
public virtual Task<T> RunOnThread<T>(Func<T> action, bool useMain)
public Task<T> RunOnThread<T>(Func<T> action, bool useMain)
{
if (useMain)
{
@@ -58,7 +58,7 @@ public abstract class ThreadContext : IThreadContext
return MainToWorker(action);
}
return RunMainAsync(action);
return RunWorkerAsync(action);
}
public async Task RunOnThreadAsync(Func<Task> action, bool useMain)
@@ -74,7 +74,7 @@ public abstract class ThreadContext : IThreadContext
await WorkerToMainAsync<object?>(async () =>
{
await action();
return Task.FromResult<object?>(null);
return s_empty;
});
}
}
@@ -85,7 +85,7 @@ public abstract class ThreadContext : IThreadContext
await MainToWorkerAsync<object?>(async () =>
{
await action();
return Task.FromResult<object?>(null);
return s_empty;
});
}
else
@@ -96,7 +96,7 @@ public abstract class ThreadContext : IThreadContext
}
else
{
await action();
await RunWorkerAsync(action);
}
}
}
@@ -117,7 +117,7 @@ public abstract class ThreadContext : IThreadContext
{
return MainToWorkerAsync(action);
}
return RunMainAsync(action);
return RunWorkerAsync(action);
}
protected abstract Task<T> WorkerToMainAsync<T>(Func<Task<T>> action);
@@ -129,9 +129,17 @@ public abstract class ThreadContext : IThreadContext
protected virtual void RunMain(Action action) => action();
protected virtual void RunWorker(Action action) => action();
protected virtual Task<T> RunMainAsync<T>(Func<T> action) => Task.FromResult(action());
protected virtual Task<T> RunWorkerAsync<T>(Func<T> action) => Task.FromResult(action());
protected virtual Task RunMainAsync(Func<Task> action) => Task.FromResult(action());
protected virtual Task RunWorkerAsync(Func<Task> action) => Task.FromResult(action());
protected virtual Task<T> RunMainAsync<T>(Func<Task<T>> action) => action();
protected virtual Task<T> RunWorkerAsync<T>(Func<Task<T>> action) => action();
}
@@ -1,11 +0,0 @@
using Speckle.InterfaceGenerator;
using Speckle.Sdk;
using Speckle.Sdk.Host;
namespace Speckle.Connectors.Common.Threading;
[GenerateAutoInterface]
public class ThreadOptions(ISpeckleApplication speckleApplication) : IThreadOptions
{
public bool RunReceiveBuildOnMainThread => speckleApplication.HostApplication != HostApplications.Rhino.Name;
}