Merge branch 'dev' into adam/cnx-1367-build-subset-when-tagged
This commit is contained in:
+6
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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(
|
||||
|
||||
+11
-7
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user