diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs index d243d12b7..180156939 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs @@ -12,15 +12,18 @@ public class ArcGISSelectionBinding : ISelectionBinding public string Name => "selectionBinding"; public IBrowserBridge Parent { get; } - public ArcGISSelectionBinding(IBrowserBridge parent, MapMembersUtils mapMemberUtils) + public ArcGISSelectionBinding( + IBrowserBridge parent, + MapMembersUtils mapMemberUtils, + ITopLevelExceptionHandler topLevelExceptionHandler + ) { _mapMemberUtils = mapMemberUtils; Parent = parent; - var topLevelHandler = parent.TopLevelExceptionHandler; // example: https://github.com/Esri/arcgis-pro-sdk-community-samples/blob/master/Map-Authoring/QueryBuilderControl/DefinitionQueryDockPaneViewModel.cs // MapViewEventArgs args = new(MapView.Active); - TOCSelectionChangedEvent.Subscribe(_ => topLevelHandler.CatchUnhandled(OnSelectionChanged), true); + TOCSelectionChangedEvent.Subscribe(_ => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged), true); } private void OnSelectionChanged() diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index 3114b7c64..ea887b66f 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -15,6 +15,7 @@ using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Exceptions; using Speckle.Connectors.DUI.Logging; using Speckle.Connectors.DUI.Models; @@ -69,7 +70,9 @@ public ArcGISSendBinding( ILogger logger, IArcGISConversionSettingsFactory arcGisConversionSettingsFactory, MapMembersUtils mapMemberUtils, - IThreadContext threadContext + IThreadContext threadContext, + IEventAggregator eventAggregator, + ITopLevelExceptionHandler topLevelExceptionHandler ) { _store = store; @@ -79,7 +82,7 @@ IThreadContext threadContext _sendConversionCache = sendConversionCache; _operationProgressManager = operationProgressManager; _logger = logger; - _topLevelExceptionHandler = parent.TopLevelExceptionHandler; + _topLevelExceptionHandler = topLevelExceptionHandler; _arcGISConversionSettingsFactory = arcGisConversionSettingsFactory; _mapMemberUtils = mapMemberUtils; _threadContext = threadContext; @@ -87,10 +90,12 @@ IThreadContext threadContext Parent = parent; Commands = new SendBindingUICommands(parent); SubscribeToArcGISEvents(); - _store.DocumentChanged += (_, _) => - { - _sendConversionCache.ClearCache(); - }; + eventAggregator + .GetEvent() + .Subscribe(_ => + { + _sendConversionCache.ClearCache(); + }); } private void SubscribeToArcGISEvents() @@ -201,7 +206,7 @@ private void SubscribeToAnyDataSourceChange(Table layerTable) { RowCreatedEvent.Subscribe( (args) => - Parent.TopLevelExceptionHandler.FireAndForget(async () => + _topLevelExceptionHandler.FireAndForget(async () => { await OnRowChanged(args); }), @@ -209,7 +214,7 @@ private void SubscribeToAnyDataSourceChange(Table layerTable) ); RowChangedEvent.Subscribe( (args) => - Parent.TopLevelExceptionHandler.FireAndForget(async () => + _topLevelExceptionHandler.FireAndForget(async () => { await OnRowChanged(args); }), @@ -217,7 +222,7 @@ private void SubscribeToAnyDataSourceChange(Table layerTable) ); RowDeletedEvent.Subscribe( (args) => - Parent.TopLevelExceptionHandler.FireAndForget(async () => + _topLevelExceptionHandler.FireAndForget(async () => { await OnRowChanged(args); }), diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index d3881899e..49b51a384 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -3,6 +3,7 @@ using Speckle.Connectors.ArcGIS.Utils; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Sdk; @@ -21,18 +22,21 @@ public class BasicConnectorBinding : IBasicConnectorBinding private readonly DocumentModelStore _store; private readonly ISpeckleApplication _speckleApplication; - public BasicConnectorBinding(DocumentModelStore store, IBrowserBridge parent, ISpeckleApplication speckleApplication) + public BasicConnectorBinding( + DocumentModelStore store, + IBrowserBridge parent, + ISpeckleApplication speckleApplication, + IEventAggregator eventAggregator + ) { _store = store; _speckleApplication = speckleApplication; Parent = parent; Commands = new BasicConnectorBindingCommands(parent); - _store.DocumentChanged += (_, _) => - parent.TopLevelExceptionHandler.FireAndForget(async () => - { - await Commands.NotifyDocumentChanged(); - }); + eventAggregator + .GetEvent() + .Subscribe(async _ => await Commands.NotifyDocumentChanged().ConfigureAwait(false)); } public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs index 301c13272..08ea8dbd9 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs @@ -37,7 +37,6 @@ public static void AddArcGIS(this IServiceCollection serviceCollection) serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(); - serviceCollection.RegisterTopLevelExceptionHandler(); serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc()); // register send operation and dependencies diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGISThreadContext.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGISThreadContext.cs index 42297eb00..8e4e2e239 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGISThreadContext.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGISThreadContext.cs @@ -1,4 +1,4 @@ -using ArcGIS.Desktop.Framework.Threading.Tasks; +using ArcGIS.Desktop.Framework.Threading.Tasks; using Speckle.Connectors.Common.Threading; namespace Speckle.Connectors.ArcGIS.Utils; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs index 0e6e5e1da..dbec9d03b 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs @@ -4,6 +4,7 @@ using ArcGIS.Desktop.Mapping.Events; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Utils; @@ -12,15 +13,18 @@ namespace Speckle.Connectors.ArcGIS.Utils; public class ArcGISDocumentStore : DocumentModelStore { private readonly IThreadContext _threadContext; + private readonly IEventAggregator _eventAggregator; public ArcGISDocumentStore( IJsonSerializer jsonSerializer, ITopLevelExceptionHandler topLevelExceptionHandler, - IThreadContext threadContext + IThreadContext threadContext, + IEventAggregator eventAggregator ) : base(jsonSerializer) { _threadContext = threadContext; + _eventAggregator = eventAggregator; ActiveMapViewChangedEvent.Subscribe(a => topLevelExceptionHandler.CatchUnhandled(() => OnMapViewChanged(a)), true); ProjectSavingEvent.Subscribe( _ => @@ -44,7 +48,7 @@ IThreadContext threadContext { IsDocumentInit = true; LoadState(); - OnDocumentChanged(); + eventAggregator.GetEvent().Publish(new object()); } } @@ -78,7 +82,7 @@ private void OnMapViewChanged(ActiveMapViewChangedEventArgs args) IsDocumentInit = true; LoadState(); - OnDocumentChanged(); + _eventAggregator.GetEvent().Publish(new object()); } protected override void HostAppSaveState(string modelCardState) => diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs index e5f8116f8..8053a6e60 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -4,6 +4,7 @@ using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Sdk; @@ -31,6 +32,7 @@ public AutocadBasicConnectorBinding( IAccountManager accountManager, ISpeckleApplication speckleApplication, ILogger logger, + IEventAggregator eventAggregator, IThreadContext threadContext ) { @@ -39,8 +41,9 @@ IThreadContext threadContext _accountManager = accountManager; _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - _store.DocumentChanged += (_, _) => - parent.TopLevelExceptionHandler.FireAndForget(async () => + eventAggregator + .GetEvent() + .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); }); diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs index 104341c33..6d3d5e7d7 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs @@ -18,9 +18,13 @@ public class AutocadSelectionBinding : ISelectionBinding public IBrowserBridge Parent { get; } - public AutocadSelectionBinding(IBrowserBridge parent, IThreadContext threadContext) + public AutocadSelectionBinding( + IBrowserBridge parent, + IThreadContext threadContext, + ITopLevelExceptionHandler topLevelExceptionHandler + ) { - _topLevelExceptionHandler = parent.TopLevelExceptionHandler; + _topLevelExceptionHandler = topLevelExceptionHandler; Parent = parent; _threadContext = threadContext; diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs index 5f556e623..7c3a7e576 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Autodesk.AutoCAD.DatabaseServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,6 +11,7 @@ using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Exceptions; using Speckle.Connectors.DUI.Logging; using Speckle.Connectors.DUI.Models; @@ -60,7 +61,9 @@ protected AutocadSendBaseBinding( IOperationProgressManager operationProgressManager, ILogger logger, ISpeckleApplication speckleApplication, - IThreadContext threadContext + ITopLevelExceptionHandler topLevelExceptionHandler, + IThreadContext threadContext, + IEventAggregator eventAggregator ) { _store = store; @@ -73,7 +76,7 @@ IThreadContext threadContext _logger = logger; _speckleApplication = speckleApplication; _threadContext = threadContext; - _topLevelExceptionHandler = parent.TopLevelExceptionHandler; + _topLevelExceptionHandler = topLevelExceptionHandler; Parent = parent; Commands = new SendBindingUICommands(parent); @@ -86,10 +89,8 @@ IThreadContext threadContext SubscribeToObjectChanges(Application.DocumentManager.CurrentDocument); } // Since ids of the objects generates from same seed, we should clear the cache always whenever doc swapped. - _store.DocumentChanged += (_, _) => - { - _sendConversionCache.ClearCache(); - }; + + eventAggregator.GetEvent().Subscribe(_ => _sendConversionCache.ClearCache()); } private readonly List _docSubsTracker = new(); diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs index ddd5c223c..e222636c6 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs @@ -6,6 +6,7 @@ using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Converters.Autocad; @@ -30,7 +31,9 @@ public AutocadSendBinding( ILogger logger, IAutocadConversionSettingsFactory autocadConversionSettingsFactory, ISpeckleApplication speckleApplication, - IThreadContext threadContext + ITopLevelExceptionHandler topLevelExceptionHandler, + IThreadContext threadContext, + IEventAggregator eventAggregator ) : base( store, @@ -43,7 +46,9 @@ IThreadContext threadContext operationProgressManager, logger, speckleApplication, - threadContext + topLevelExceptionHandler, + threadContext, + eventAggregator ) { _autocadConversionSettingsFactory = autocadConversionSettingsFactory; diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/SharedRegistration.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/SharedRegistration.cs index 8ce12c190..6e2db8da2 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/SharedRegistration.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/DependencyInjection/SharedRegistration.cs @@ -61,8 +61,6 @@ public static void AddAutocadBase(this IServiceCollection serviceCollection) serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - - serviceCollection.RegisterTopLevelExceptionHandler(); } public static void LoadSend(this IServiceCollection serviceCollection) diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs index c8319d638..eb8796cb1 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs @@ -1,4 +1,5 @@ using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Utils; @@ -9,15 +10,18 @@ public class AutocadDocumentStore : DocumentModelStore private readonly string _nullDocumentName = "Null Doc"; private string _previousDocName; private readonly AutocadDocumentManager _autocadDocumentManager; + private readonly IEventAggregator _eventAggregator; public AutocadDocumentStore( IJsonSerializer jsonSerializer, AutocadDocumentManager autocadDocumentManager, - ITopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler, + IEventAggregator eventAggregator ) : base(jsonSerializer) { _autocadDocumentManager = autocadDocumentManager; + _eventAggregator = eventAggregator; _previousDocName = _nullDocumentName; // POC: Will be addressed to move it into AutocadContext! @@ -48,7 +52,7 @@ private void OnDocChangeInternal(Document? doc) _previousDocName = currentDocName; LoadState(); - OnDocumentChanged(); + _eventAggregator.GetEvent().Publish(new object()); } protected override void LoadState() diff --git a/Connectors/Autocad/Speckle.Connectors.Civil3dShared/Bindings/Civil3dSendBinding.cs b/Connectors/Autocad/Speckle.Connectors.Civil3dShared/Bindings/Civil3dSendBinding.cs index 4b26c74ef..3ff8e014e 100644 --- a/Connectors/Autocad/Speckle.Connectors.Civil3dShared/Bindings/Civil3dSendBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.Civil3dShared/Bindings/Civil3dSendBinding.cs @@ -7,6 +7,7 @@ using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Converters.Autocad; @@ -34,7 +35,9 @@ public Civil3dSendBinding( ICivil3dConversionSettingsFactory civil3dConversionSettingsFactory, IAutocadConversionSettingsFactory autocadConversionSettingsFactory, ISpeckleApplication speckleApplication, - IThreadContext threadContext + ITopLevelExceptionHandler topLevelExceptionHandler, + IThreadContext threadContext, + IEventAggregator eventAggregator ) : base( store, @@ -47,7 +50,9 @@ IThreadContext threadContext operationProgressManager, logger, speckleApplication, - threadContext + topLevelExceptionHandler, + threadContext, + eventAggregator ) { _civil3dConversionSettingsFactory = civil3dConversionSettingsFactory; diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs index 59945da1e..a74afb7d8 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs @@ -43,8 +43,6 @@ public static IServiceCollection AddCsi(this IServiceCollection services) services.AddScoped, CsiRootObjectBuilder>(); services.AddScoped>(); - services.RegisterTopLevelExceptionHandler(); - return services; } } diff --git a/Connectors/Navisworks/Speckle.Connectors.NavisworksShared/DependencyInjection/NavisworksConnectorServiceRegistration.cs b/Connectors/Navisworks/Speckle.Connectors.NavisworksShared/DependencyInjection/NavisworksConnectorServiceRegistration.cs index b906ddce4..7d633dcae 100644 --- a/Connectors/Navisworks/Speckle.Connectors.NavisworksShared/DependencyInjection/NavisworksConnectorServiceRegistration.cs +++ b/Connectors/Navisworks/Speckle.Connectors.NavisworksShared/DependencyInjection/NavisworksConnectorServiceRegistration.cs @@ -59,7 +59,6 @@ public static void AddNavisworks(this IServiceCollection serviceCollection) serviceCollection.AddSingleton(); // Register Intercom/interop - serviceCollection.RegisterTopLevelExceptionHandler(); serviceCollection.AddTransient(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index c4ce8e66c..9f8d681e9 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -1,5 +1,6 @@ using Autodesk.Revit.DB; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Connectors.RevitShared; @@ -26,7 +27,8 @@ public BasicConnectorBindingRevit( DocumentModelStore store, IBrowserBridge parent, RevitContext revitContext, - ISpeckleApplication speckleApplication + ISpeckleApplication speckleApplication, + IEventAggregator eventAggregator ) { Name = "baseBinding"; @@ -37,8 +39,9 @@ ISpeckleApplication speckleApplication Commands = new BasicConnectorBindingCommands(parent); // POC: event binding? - _store.DocumentChanged += (_, _) => - parent.TopLevelExceptionHandler.FireAndForget(async () => + eventAggregator + .GetEvent() + .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); }); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index 27cb51e56..17b4901e3 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -8,6 +8,7 @@ using Speckle.Connectors.Common.Operations; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Exceptions; using Speckle.Connectors.DUI.Logging; using Speckle.Connectors.DUI.Models; @@ -59,7 +60,9 @@ public RevitSendBinding( ILogger logger, ElementUnpacker elementUnpacker, IRevitConversionSettingsFactory revitConversionSettingsFactory, - ISpeckleApplication speckleApplication + ISpeckleApplication speckleApplication, + IEventAggregator eventAggregator, + ITopLevelExceptionHandler topLevelExceptionHandler ) : base("sendBinding", store, bridge, revitContext) { @@ -73,7 +76,6 @@ ISpeckleApplication speckleApplication _elementUnpacker = elementUnpacker; _revitConversionSettingsFactory = revitConversionSettingsFactory; _speckleApplication = speckleApplication; - var topLevelExceptionHandler = Parent.TopLevelExceptionHandler; Commands = new SendBindingUICommands(bridge); // TODO expiry events @@ -81,7 +83,12 @@ ISpeckleApplication speckleApplication revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) => topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e)); - Store.DocumentChanged += (_, _) => topLevelExceptionHandler.FireAndForget(async () => await OnDocumentChanged()); + eventAggregator + .GetEvent() + .Subscribe(async _ => + { + await OnDocumentChanged().ConfigureAwait(false); + }); } public List GetSendFilters() => diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs index eabb03f7f..a5903928f 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs @@ -17,6 +17,7 @@ public SelectionBinding( RevitContext revitContext, DocumentModelStore store, IAppIdleManager revitIdleManager, + ITopLevelExceptionHandler topLevelExceptionHandler, IBrowserBridge parent ) : base("selectionBinding", store, parent, revitContext) @@ -24,7 +25,7 @@ IBrowserBridge parent #if REVIT2022 // NOTE: getting the selection data should be a fast function all, even for '000s of elements - and having a timer hitting it every 1s is ok. _selectionTimer = new System.Timers.Timer(1000); - _selectionTimer.Elapsed += (_, _) => parent.TopLevelExceptionHandler.CatchUnhandled(OnSelectionChanged); + _selectionTimer.Elapsed += (_, _) => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged); _selectionTimer.Start(); #else diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs index b719cc2d4..59dc9da5f 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs @@ -43,8 +43,6 @@ public static void AddRevit(this IServiceCollection serviceCollection) serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.RegisterTopLevelExceptionHandler(); - serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index 5b8629853..8ddf7aa58 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -3,6 +3,7 @@ using Autodesk.Revit.UI; using Autodesk.Revit.UI.Events; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Utils; using Speckle.Converters.RevitShared.Helpers; @@ -20,6 +21,7 @@ internal sealed class RevitDocumentStore : DocumentModelStore private readonly IAppIdleManager _idleManager; private readonly DocumentModelStorageSchema _documentModelStorageSchema; private readonly IdStorageSchema _idStorageSchema; + private readonly IEventAggregator _eventAggregator; public RevitDocumentStore( IAppIdleManager idleManager, @@ -27,6 +29,7 @@ public RevitDocumentStore( IJsonSerializer jsonSerializer, DocumentModelStorageSchema documentModelStorageSchema, IdStorageSchema idStorageSchema, + IEventAggregator eventAggregator, ITopLevelExceptionHandler topLevelExceptionHandler ) : base(jsonSerializer) @@ -35,6 +38,7 @@ ITopLevelExceptionHandler topLevelExceptionHandler _revitContext = revitContext; _documentModelStorageSchema = documentModelStorageSchema; _idStorageSchema = idStorageSchema; + _eventAggregator = eventAggregator; UIApplication uiApplication = _revitContext.UIApplication.NotNull(); @@ -49,7 +53,8 @@ ITopLevelExceptionHandler topLevelExceptionHandler // There is no event that we can hook here for double-click file open... // It is kind of harmless since we create this object as "SingleInstance". LoadState(); - OnDocumentChanged(); + + eventAggregator.GetEvent().Publish(new object()); } /// @@ -74,7 +79,7 @@ private void OnViewActivated(object? _, ViewActivatedEventArgs e) () => { LoadState(); - OnDocumentChanged(); + _eventAggregator.GetEvent().Publish(new object()); } ); } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitThreadContext.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitThreadContext.cs index 957b3959c..17ce36122 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitThreadContext.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitThreadContext.cs @@ -1,4 +1,4 @@ -using Revit.Async; +using Revit.Async; using Speckle.Connectors.Common.Threading; namespace Speckle.Connectors.Revit.Plugin; diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs index b34e002d0..1cdc94ed1 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs @@ -4,6 +4,7 @@ using Speckle.Connectors.Common.Caching; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Connectors.Rhino.Extensions; @@ -26,7 +27,8 @@ public RhinoBasicConnectorBinding( DocumentModelStore store, IBrowserBridge parent, ISendConversionCache sendConversionCache, - ISpeckleApplication speckleApplication + ISpeckleApplication speckleApplication, + IEventAggregator eventAggregator ) { _store = store; @@ -35,8 +37,9 @@ ISpeckleApplication speckleApplication _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - _store.DocumentChanged += (_, _) => - parent.TopLevelExceptionHandler.FireAndForget(async () => + eventAggregator + .GetEvent() + .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); // Note: this prevents scaling issues when copy-pasting from one rhino doc to another in the same session. diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs index d2679b8b3..98b1c9480 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs @@ -2,29 +2,30 @@ using Rhino.DocObjects; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; +using Speckle.Connectors.RhinoShared; namespace Speckle.Connectors.Rhino.Bindings; public class RhinoSelectionBinding : ISelectionBinding { - private readonly IAppIdleManager _idleManager; private const string SELECTION_EVENT = "setSelection"; + private readonly IEventAggregator _eventAggregator; public string Name => "selectionBinding"; public IBrowserBridge Parent { get; } - public RhinoSelectionBinding(IAppIdleManager idleManager, IBrowserBridge parent) + public RhinoSelectionBinding(IBrowserBridge parent, IEventAggregator eventAggregator) { - _idleManager = idleManager; Parent = parent; - - RhinoDoc.SelectObjects += OnSelectionChange; - RhinoDoc.DeselectObjects += OnSelectionChange; - RhinoDoc.DeselectAllObjects += OnSelectionChange; + _eventAggregator = eventAggregator; + eventAggregator.GetEvent().Subscribe(OnSelectionChange); + eventAggregator.GetEvent().Subscribe(OnSelectionChange); + eventAggregator.GetEvent().Subscribe(OnSelectionChange); } - private void OnSelectionChange(object? o, EventArgs eventArgs) => - _idleManager.SubscribeToIdle(nameof(RhinoSelectionBinding), UpdateSelection); + private void OnSelectionChange(EventArgs eventArgs) => + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSelectionBinding), UpdateSelection); private void UpdateSelection() { diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs index 61e389faa..0e768db80 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs @@ -10,12 +10,14 @@ using Speckle.Connectors.Common.Operations; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Exceptions; using Speckle.Connectors.DUI.Logging; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.Settings; +using Speckle.Connectors.RhinoShared; using Speckle.Converters.Common; using Speckle.Converters.Rhino; using Speckle.Sdk; @@ -31,14 +33,12 @@ public sealed class RhinoSendBinding : ISendBinding public IBrowserBridge Parent { get; } private readonly DocumentModelStore _store; - private readonly IAppIdleManager _idleManager; private readonly IServiceProvider _serviceProvider; private readonly List _sendFilters; private readonly CancellationManager _cancellationManager; private readonly ISendConversionCache _sendConversionCache; private readonly IOperationProgressManager _operationProgressManager; private readonly ILogger _logger; - private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; private readonly IRhinoConversionSettingsFactory _rhinoConversionSettingsFactory; private readonly ISpeckleApplication _speckleApplication; private readonly ISdkActivityFactory _activityFactory; @@ -63,7 +63,6 @@ public sealed class RhinoSendBinding : ISendBinding public RhinoSendBinding( DocumentModelStore store, - IAppIdleManager idleManager, IBrowserBridge parent, IEnumerable sendFilters, IServiceProvider serviceProvider, @@ -73,11 +72,11 @@ public RhinoSendBinding( ILogger logger, IRhinoConversionSettingsFactory rhinoConversionSettingsFactory, ISpeckleApplication speckleApplication, - ISdkActivityFactory activityFactory + ISdkActivityFactory activityFactory, + IEventAggregator eventAggregator ) { _store = store; - _idleManager = idleManager; _serviceProvider = serviceProvider; _sendFilters = sendFilters.ToList(); _cancellationManager = cancellationManager; @@ -86,15 +85,14 @@ ISdkActivityFactory activityFactory _logger = logger; _rhinoConversionSettingsFactory = rhinoConversionSettingsFactory; _speckleApplication = speckleApplication; - _topLevelExceptionHandler = parent.TopLevelExceptionHandler.Parent.TopLevelExceptionHandler; Parent = parent; Commands = new SendBindingUICommands(parent); // POC: Commands are tightly coupled with their bindings, at least for now, saves us injecting a factory. _activityFactory = activityFactory; PreviousUnitSystem = RhinoDoc.ActiveDoc.ModelUnitSystem; - SubscribeToRhinoEvents(); + SubscribeToRhinoEvents(eventAggregator); } - private void SubscribeToRhinoEvents() + private void SubscribeToRhinoEvents(IEventAggregator eventAggregator) { Command.BeginCommand += (_, e) => { @@ -110,29 +108,33 @@ private void SubscribeToRhinoEvents() { ChangedObjectIdsInGroupsOrLayers[selectedObject.Id.ToString()] = 1; } - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); } }; - - RhinoDoc.ActiveDocumentChanged += (_, e) => - { - PreviousUnitSystem = e.Document.ModelUnitSystem; - }; + eventAggregator + .GetEvent() + .Subscribe(e => + { + PreviousUnitSystem = e.Document.ModelUnitSystem; + }); // NOTE: BE CAREFUL handling things in this event handler since it is triggered whenever we save something into file! - RhinoDoc.DocumentPropertiesChanged += async (_, e) => - { - var newUnit = e.Document.ModelUnitSystem; - if (newUnit != PreviousUnitSystem) + eventAggregator + .GetEvent() + .Subscribe(async e => { - PreviousUnitSystem = newUnit; + var newUnit = e.Document.ModelUnitSystem; + if (newUnit != PreviousUnitSystem) + { + PreviousUnitSystem = newUnit; - await InvalidateAllSender(); - } - }; + await InvalidateAllSender(); + } + }); - RhinoDoc.AddRhinoObject += (_, e) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(e => { if (!_store.IsDocumentInit) { @@ -140,11 +142,12 @@ private void SubscribeToRhinoEvents() } ChangedObjectIds[e.ObjectId.ToString()] = 1; - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); }); - RhinoDoc.DeleteRhinoObject += (_, e) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(e => { if (!_store.IsDocumentInit) { @@ -152,12 +155,13 @@ private void SubscribeToRhinoEvents() } ChangedObjectIds[e.ObjectId.ToString()] = 1; - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); }); // NOTE: Catches an object's material change from one user defined doc material to another. Does not catch (as the top event is not triggered) swapping material sources for an object or moving to/from the default material (this is handled below)! - RhinoDoc.RenderMaterialsTableEvent += (_, args) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(args => { if (!_store.IsDocumentInit) { @@ -167,12 +171,13 @@ private void SubscribeToRhinoEvents() if (args is RhinoDoc.RenderMaterialAssignmentChangedEventArgs changedEventArgs) { ChangedObjectIds[changedEventArgs.ObjectId.ToString()] = 1; - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); } }); - RhinoDoc.GroupTableEvent += (_, args) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(args => { if (!_store.IsDocumentInit) { @@ -183,11 +188,12 @@ private void SubscribeToRhinoEvents() { ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; } - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); }); - RhinoDoc.LayerTableEvent += (_, args) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(args => { if (!_store.IsDocumentInit) { @@ -215,12 +221,13 @@ private void SubscribeToRhinoEvents() ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; } } - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); }); // Catches and stores changed material ids. These are then used in the expiry checks to invalidate all objects that have assigned any of those material ids. - RhinoDoc.MaterialTableEvent += (_, args) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(args => { if (!_store.IsDocumentInit) { @@ -230,12 +237,13 @@ private void SubscribeToRhinoEvents() if (args.EventType == MaterialTableEventType.Modified) { ChangedMaterialIndexes[args.Index] = 1; - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); } }); - RhinoDoc.ModifyObjectAttributes += (_, e) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(e => { if (!_store.IsDocumentInit) { @@ -251,12 +259,13 @@ private void SubscribeToRhinoEvents() ) { ChangedObjectIds[e.RhinoObject.Id.ToString()] = 1; - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); } }); - RhinoDoc.ReplaceRhinoObject += (_, e) => - _topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator + .GetEvent() + .Subscribe(e => { if (!_store.IsDocumentInit) { @@ -265,7 +274,7 @@ private void SubscribeToRhinoEvents() ChangedObjectIds[e.NewRhinoObject.Id.ToString()] = 1; ChangedObjectIds[e.OldRhinoObject.Id.ToString()] = 1; - _idleManager.SubscribeToIdle(nameof(RhinoSendBinding), RunExpirationChecks); + eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); }); } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs new file mode 100644 index 000000000..af3910f47 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs @@ -0,0 +1,77 @@ +using Rhino; +using Rhino.DocObjects; +using Rhino.DocObjects.Tables; +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; + +namespace Speckle.Connectors.RhinoShared; + +public class BeginOpenDocument(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class EndOpenDocument(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class SelectObjects(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DeselectObjects(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DeselectAllObjects(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class ActiveDocumentChanged(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DocumentPropertiesChanged(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class AddRhinoObject(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DeleteRhinoObject(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class RenderMaterialsTableEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class MaterialTableEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class ModifyObjectAttributes(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class ReplaceRhinoObject(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class GroupTableEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class LayerTableEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public static class RhinoEvents +{ + public static void Register(IEventAggregator eventAggregator) + { + RhinoApp.Idle += (_, e) => eventAggregator.GetEvent().Publish(e); + + RhinoDoc.BeginOpenDocument += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.EndOpenDocument += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.SelectObjects += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.DeselectObjects += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.DeselectAllObjects += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.ActiveDocumentChanged += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.DocumentPropertiesChanged += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.AddRhinoObject += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.DeleteRhinoObject += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.RenderMaterialsTableEvent += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.MaterialTableEvent += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.ModifyObjectAttributes += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.ReplaceRhinoObject += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.GroupTableEvent += (_, e) => eventAggregator.GetEvent().Publish(e); + RhinoDoc.LayerTableEvent += (_, e) => eventAggregator.GetEvent().Publish(e); + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs index a921eb153..2af952d34 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs @@ -1,7 +1,8 @@ using Rhino; -using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Utils; +using Speckle.Connectors.RhinoShared; namespace Speckle.Connectors.Rhino.HostApp; @@ -10,12 +11,13 @@ public class RhinoDocumentStore : DocumentModelStore private const string SPECKLE_KEY = "Speckle_DUI3"; public override bool IsDocumentInit { get; set; } = true; // Note: because of rhino implementation details regarding expiry checking of sender cards. - public RhinoDocumentStore(IJsonSerializer jsonSerializer, ITopLevelExceptionHandler topLevelExceptionHandler) + public RhinoDocumentStore(IJsonSerializer jsonSerializer, IEventAggregator eventAggregator) : base(jsonSerializer) { - RhinoDoc.BeginOpenDocument += (_, _) => topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false); - RhinoDoc.EndOpenDocument += (_, e) => - topLevelExceptionHandler.CatchUnhandled(() => + eventAggregator.GetEvent().Subscribe(_ => IsDocumentInit = false); + eventAggregator + .GetEvent() + .Subscribe(e => { if (e.Merge) { @@ -29,7 +31,7 @@ public RhinoDocumentStore(IJsonSerializer jsonSerializer, ITopLevelExceptionHand IsDocumentInit = true; LoadState(); - OnDocumentChanged(); + eventAggregator.GetEvent().Publish(new object()); }); } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoIdleManager.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoIdleManager.cs deleted file mode 100644 index f536f5786..000000000 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoIdleManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Rhino; -using Speckle.Connectors.DUI.Bridge; - -namespace Speckle.Connectors.Rhino.HostApp; - -/// -/// Rhino Idle Manager is a helper util to manage deferred actions. -/// -public sealed class RhinoIdleManager(IIdleCallManager idleCallManager) : AppIdleManager(idleCallManager) -{ - private readonly IIdleCallManager _idleCallManager = idleCallManager; - - protected override void AddEvent() - { - RhinoApp.Idle += RhinoAppOnIdle; - } - - private void RhinoAppOnIdle(object? sender, EventArgs e) => - _idleCallManager.AppOnIdle(() => RhinoApp.Idle -= RhinoAppOnIdle); -} diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/RhinoPlugin.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/RhinoPlugin.cs deleted file mode 100644 index 3a060c253..000000000 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/RhinoPlugin.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Rhino; -using Speckle.Connectors.DUI.Bridge; -using Speckle.Connectors.Rhino.Plugin; -using Speckle.InterfaceGenerator; - -namespace Speckle.Connectors.Rhino.DependencyInjection; - -[GenerateAutoInterface] -public class RhinoPlugin : IRhinoPlugin -{ - private readonly IAppIdleManager _idleManager; - - public RhinoPlugin(IAppIdleManager idleManager) - { - _idleManager = idleManager; - } - - public void Initialise() => - _idleManager.SubscribeToIdle( - nameof(RhinoPlugin), - () => RhinoApp.RunScript(SpeckleConnectorsRhinoCommand.Instance.EnglishName, false) - ); - - public void Shutdown() { } -} diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/Speckle.Connectors.RhinoPlugin.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/Speckle.Connectors.RhinoPlugin.cs index a1158578e..61052675c 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/Speckle.Connectors.RhinoPlugin.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Plugin/Speckle.Connectors.RhinoPlugin.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.DependencyInjection; using Rhino.PlugIns; using Speckle.Connectors.Common; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.Rhino.DependencyInjection; +using Speckle.Connectors.RhinoShared; using Speckle.Converters.Rhino; using Speckle.Sdk; using Speckle.Sdk.Host; @@ -19,7 +21,6 @@ namespace Speckle.Connectors.Rhino.Plugin; /// public class SpeckleConnectorsRhinoPlugin : PlugIn { - private IRhinoPlugin? _rhinoPlugin; private IDisposable? _disposableLogger; protected override string LocalPlugInName => "Speckle (Beta) for Rhino"; @@ -52,9 +53,7 @@ protected override LoadReturnCode OnLoad(ref string errorMessage) // but the Rhino connector has `.rhp` as it is extension. Container = services.BuildServiceProvider(); - // Resolve root plugin object and initialise. - _rhinoPlugin = Container.GetRequiredService(); - _rhinoPlugin.Initialise(); + RhinoEvents.Register(Container.GetRequiredService()); return LoadReturnCode.Success; } @@ -78,7 +77,6 @@ private HostAppVersion GetVersion() protected override void OnShutdown() { - _rhinoPlugin?.Shutdown(); _disposableLogger?.Dispose(); Container?.Dispose(); base.OnShutdown(); diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Registration/ServiceRegistration.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Registration/ServiceRegistration.cs index a5ad1c12d..82bf48c49 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Registration/ServiceRegistration.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Registration/ServiceRegistration.cs @@ -11,7 +11,6 @@ using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI; using Speckle.Connectors.DUI.Bindings; -using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.WebView; using Speckle.Connectors.Rhino.Bindings; @@ -36,17 +35,11 @@ public static void AddRhino(this IServiceCollection serviceCollection) serviceCollection.AddDUI(); serviceCollection.AddDUIView(); - // Register other connector specific types - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - // Register bindings serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); // POC: Easier like this for now, should be cleaned up later serviceCollection.AddSingleton(); - serviceCollection.RegisterTopLevelExceptionHandler(); - serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(); diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems index 42d4d5842..766cef7d8 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems @@ -21,6 +21,7 @@ + @@ -32,7 +33,6 @@ - @@ -43,7 +43,6 @@ - diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs index 519ac132e..a1e9d2c54 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Sdk; @@ -25,6 +26,7 @@ public TeklaBasicConnectorBinding( ISpeckleApplication speckleApplication, DocumentModelStore store, ILogger logger, + IEventAggregator eventAggregator, TSM.Model model ) { @@ -34,8 +36,9 @@ TSM.Model model _logger = logger; _model = model; Commands = new BasicConnectorBindingCommands(parent); - _store.DocumentChanged += (_, _) => - parent.TopLevelExceptionHandler.FireAndForget(async () => + eventAggregator + .GetEvent() + .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); }); diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs index 710d0d5b5..2fbcca3e0 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs @@ -1,41 +1,38 @@ using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; +using Speckle.Connectors.RhinoShared; using Tekla.Structures.Model; namespace Speckle.Connectors.TeklaShared.Bindings; public class TeklaSelectionBinding : ISelectionBinding { - private readonly IAppIdleManager _idleManager; private const string SELECTION_EVENT = "setSelection"; - private readonly Events _events; private readonly object _selectionEventHandlerLock = new object(); private readonly Tekla.Structures.Model.UI.ModelObjectSelector _selector; + private readonly IEventAggregator _eventAggregator; public string Name => "selectionBinding"; public IBrowserBridge Parent { get; } public TeklaSelectionBinding( - IAppIdleManager idleManager, IBrowserBridge parent, - Events events, - Tekla.Structures.Model.UI.ModelObjectSelector selector + Tekla.Structures.Model.UI.ModelObjectSelector selector, + IEventAggregator eventAggregator ) { - _idleManager = idleManager; Parent = parent; - _events = events; _selector = selector; + _eventAggregator = eventAggregator; - _events.SelectionChange += Events_SelectionChangeEvent; - _events.Register(); + eventAggregator.GetEvent().Subscribe(_ => Events_SelectionChangeEvent()); } private void Events_SelectionChangeEvent() { lock (_selectionEventHandlerLock) { - _idleManager.SubscribeToIdle(nameof(TeklaSelectionBinding), UpdateSelection); UpdateSelection(); } } @@ -43,7 +40,7 @@ private void Events_SelectionChangeEvent() private void UpdateSelection() { SelectionInfo selInfo = GetSelection(); - Parent.Send(SELECTION_EVENT, selInfo); + Parent.Send2(SELECTION_EVENT, selInfo); } public SelectionInfo GetSelection() diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSendBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSendBinding.cs index cff0f1198..b02514ae9 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSendBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSendBinding.cs @@ -6,12 +6,14 @@ using Speckle.Connectors.Common.Operations; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Exceptions; using Speckle.Connectors.DUI.Logging; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.Settings; +using Speckle.Connectors.RhinoShared; using Speckle.Connectors.TeklaShared.Operations.Send.Settings; using Speckle.Converters.Common; using Speckle.Converters.TeklaShared; @@ -24,14 +26,13 @@ namespace Speckle.Connectors.TeklaShared.Bindings; -public sealed class TeklaSendBinding : ISendBinding, IDisposable +public sealed class TeklaSendBinding : ISendBinding { public string Name => "sendBinding"; public SendBindingUICommands Commands { get; } public IBrowserBridge Parent { get; } private readonly DocumentModelStore _store; - private readonly IAppIdleManager _idleManager; private readonly IServiceProvider _serviceProvider; private readonly List _sendFilters; private readonly CancellationManager _cancellationManager; @@ -42,14 +43,12 @@ public sealed class TeklaSendBinding : ISendBinding, IDisposable private readonly ISpeckleApplication _speckleApplication; private readonly ISdkActivityFactory _activityFactory; private readonly Model _model; - private readonly Events _events; private readonly ToSpeckleSettingsManager _toSpeckleSettingsManager; private ConcurrentDictionary ChangedObjectIds { get; set; } = new(); public TeklaSendBinding( DocumentModelStore store, - IAppIdleManager idleManager, IBrowserBridge parent, IEnumerable sendFilters, IServiceProvider serviceProvider, @@ -60,11 +59,11 @@ public TeklaSendBinding( ITeklaConversionSettingsFactory teklaConversionSettingsFactory, ISpeckleApplication speckleApplication, ISdkActivityFactory activityFactory, - ToSpeckleSettingsManager toSpeckleSettingsManager + ToSpeckleSettingsManager toSpeckleSettingsManager, + IEventAggregator eventAggregator ) { _store = store; - _idleManager = idleManager; _serviceProvider = serviceProvider; _sendFilters = sendFilters.ToList(); _cancellationManager = cancellationManager; @@ -79,14 +78,7 @@ ToSpeckleSettingsManager toSpeckleSettingsManager _toSpeckleSettingsManager = toSpeckleSettingsManager; _model = new Model(); - _events = new Events(); - SubscribeToTeklaEvents(); - } - - private void SubscribeToTeklaEvents() - { - _events.ModelObjectChanged += ModelHandler_OnChange; - _events.Register(); + eventAggregator.GetEvent().Subscribe(ModelHandler_OnChange); } // subscribes the all changes in a modelobject @@ -195,15 +187,4 @@ private async Task RunExpirationChecks() ChangedObjectIds = new ConcurrentDictionary(); } - - private bool _disposed; - - public void Dispose() - { - if (!_disposed) - { - _events.UnRegister(); - _disposed = true; - } - } } diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Events.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Events.cs new file mode 100644 index 000000000..24b749eef --- /dev/null +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Events.cs @@ -0,0 +1,26 @@ +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; + +namespace Speckle.Connectors.RhinoShared; + +public class SelectionChange(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class ModelObjectChanged(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent>(threadContext, exceptionHandler); + +public class ModelLoad(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public static class TeklaEvents +{ + public static void Register(Tekla.Structures.Model.Events events, IEventAggregator eventAggregator) + { + events.UnRegister(); + events.SelectionChange += () => eventAggregator.GetEvent().Publish(new object()); + events.ModelObjectChanged += x => eventAggregator.GetEvent().Publish(x); + events.ModelLoad += () => eventAggregator.GetEvent().Publish(new object()); + events.Register(); + } +} diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs index 5f75cb37e..f9e7f1945 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Logging; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Utils; +using Speckle.Connectors.RhinoShared; using Speckle.Sdk; using Speckle.Sdk.Helpers; using Speckle.Sdk.SQLite; @@ -11,33 +13,33 @@ public class TeklaDocumentModelStore : DocumentModelStore { private readonly ILogger _logger; private readonly ISqLiteJsonCacheManager _jsonCacheManager; - private readonly TSM.Events _events; private readonly TSM.Model _model; private string? _modelKey; public TeklaDocumentModelStore( IJsonSerializer jsonSerializer, ILogger logger, - ISqLiteJsonCacheManagerFactory jsonCacheManagerFactory + ISqLiteJsonCacheManagerFactory jsonCacheManagerFactory, + IEventAggregator eventAggregator ) : base(jsonSerializer) { _logger = logger; _jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData"); - _events = new TSM.Events(); _model = new TSM.Model(); GenerateKey(); - _events.ModelLoad += () => - { - GenerateKey(); - LoadState(); - OnDocumentChanged(); - }; - _events.Register(); + eventAggregator + .GetEvent() + .Publish(() => + { + GenerateKey(); + LoadState(); + eventAggregator.GetEvent().Publish(new object()); + }); if (SpeckleTeklaPanelHost.IsInitialized) { LoadState(); - OnDocumentChanged(); + eventAggregator.GetEvent().Publish(new object()); } } diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaIdleManager.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaIdleManager.cs deleted file mode 100644 index 851020e61..000000000 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaIdleManager.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Speckle.Connectors.DUI.Bridge; -using Tekla.Structures.Model; - -namespace Speckle.Connectors.TeklaShared.HostApp; - -public sealed class TeklaIdleManager : AppIdleManager -{ - private readonly IIdleCallManager _idleCallManager; - private readonly Events _events; - - public TeklaIdleManager(IIdleCallManager idleCallManager, Events events) - : base(idleCallManager) - { - _idleCallManager = idleCallManager; - _events = events; - } - - protected override void AddEvent() - { - _events.ModelSave += TeklaEventsOnIdle; - _events.Register(); - } - - private void TeklaEventsOnIdle() - { - _idleCallManager.AppOnIdle(() => - { - _events.ModelSave -= TeklaEventsOnIdle; - _events.UnRegister(); - }); - } -} diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/ServiceRegistration.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/ServiceRegistration.cs index d9d42e0cf..f058061e8 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/ServiceRegistration.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/ServiceRegistration.cs @@ -35,15 +35,11 @@ public static IServiceCollection AddTekla(this IServiceCollection services) services.AddDUI(); services.AddDUIView(); - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.RegisterTopLevelExceptionHandler(); - services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Speckle.Connectors.TeklaShared.projitems b/Connectors/Tekla/Speckle.Connector.TeklaShared/Speckle.Connectors.TeklaShared.projitems index e9782ec4d..330527ed7 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Speckle.Connectors.TeklaShared.projitems +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Speckle.Connectors.TeklaShared.projitems @@ -17,13 +17,13 @@ + - diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs index e957a4d17..b7a938698 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/SpeckleTeklaPanelHost.cs @@ -5,7 +5,9 @@ using System.Windows.Forms.Integration; using Microsoft.Extensions.DependencyInjection; using Speckle.Connectors.Common; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.WebView; +using Speckle.Connectors.RhinoShared; using Speckle.Converters.TeklaShared; using Speckle.Sdk.Host; using Tekla.Structures.Dialog; @@ -89,6 +91,7 @@ private void InitializeInstance() services.AddTeklaConverters(); Container = services.BuildServiceProvider(); + TeklaEvents.Register(Container.GetRequiredService(), Container.GetRequiredService()); Model = new Model(); if (!Model.GetConnectionStatus()) diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs index 9c8397023..611f6af7f 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/IdleCallManagerTests.cs @@ -15,7 +15,7 @@ public void SubscribeToIdleTest() var sut = new IdleCallManager(handler.Object); var action = Create(); var addEvent = Create(); - handler.Setup(x => x.CatchUnhandled(It.IsAny())); + handler.Setup(x => x.CatchUnhandled(It.IsAny())).Returns(new Result()); sut.SubscribeToIdle("id", action.Object, addEvent.Object); } diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs index 8ba16dc14..cfa7deebb 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Bridge/TopLevelExceptionHandlerTests.cs @@ -2,8 +2,9 @@ using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using Speckle.Connectors.DUI.Bindings; +using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Testing; namespace Speckle.Connectors.DUI.Tests.Bridge; @@ -14,8 +15,8 @@ public class TopLevelExceptionHandlerTests : MoqTest public void CatchUnhandledAction_Happy() { var logger = Create>(MockBehavior.Loose); - var bridge = Create(); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var eventAggregator = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); sut.CatchUnhandled(() => { }); } @@ -24,13 +25,13 @@ public void CatchUnhandledAction_Happy() public void CatchUnhandledAction_Exception() { var logger = Create>(MockBehavior.Loose); - var bridge = Create(); + var eventAggregator = Create(); - bridge - .Setup(x => x.Send(BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, It.IsAny(), default)) - .Returns(Task.CompletedTask); + eventAggregator + .Setup(x => x.GetEvent()) + .Returns(new ExceptionEvent(Create().Object, Create().Object)); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); sut.CatchUnhandled(() => throw new InvalidOperationException()); } @@ -40,8 +41,8 @@ public void CatchUnhandledFunc_Happy() { var val = 2; var logger = Create>(MockBehavior.Loose); - var bridge = Create(); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var eventAggregator = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); var returnVal = sut.CatchUnhandled(() => val); returnVal.Value.Should().Be(val); @@ -53,13 +54,13 @@ public void CatchUnhandledFunc_Happy() public void CatchUnhandledFunc_Exception() { var logger = Create>(MockBehavior.Loose); - var bridge = Create(); + var eventAggregator = Create(); - bridge - .Setup(x => x.Send(BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, It.IsAny(), default)) - .Returns(Task.CompletedTask); + eventAggregator + .Setup(x => x.GetEvent()) + .Returns(new ExceptionEvent(Create().Object, Create().Object)); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); var returnVal = sut.CatchUnhandled((Func)(() => throw new InvalidOperationException())); returnVal.Value.Should().BeNull(); @@ -71,13 +72,12 @@ public void CatchUnhandledFunc_Exception() public void CatchUnhandledFunc_Exception_Fatal() { var logger = Create>(MockBehavior.Loose); - var bridge = Create(); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var eventAggregator = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); - var exception = Assert.Throws( + Assert.Throws( () => sut.CatchUnhandled(new Func(() => throw new AppDomainUnloadedException())) ); - exception.InnerExceptions.Single().Should().BeOfType(); } [Test] @@ -85,8 +85,8 @@ public async Task CatchUnhandledFuncAsync_Happy() { var val = 2; var logger = Create>(MockBehavior.Loose); - var bridge = Create(); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var eventAggregator = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); var returnVal = await sut.CatchUnhandledAsync(() => Task.FromResult(val)); returnVal.Value.Should().Be(val); @@ -98,13 +98,13 @@ public async Task CatchUnhandledFuncAsync_Happy() public async Task CatchUnhandledFuncAsync_Exception() { var logger = Create>(MockBehavior.Loose); - var bridge = Create(); + var eventAggregator = Create(); - bridge - .Setup(x => x.Send(BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, It.IsAny(), default)) - .Returns(Task.CompletedTask); + eventAggregator + .Setup(x => x.GetEvent()) + .Returns(new ExceptionEvent(Create().Object, Create().Object)); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); var returnVal = await sut.CatchUnhandledAsync(new Func>(() => throw new InvalidOperationException())); returnVal.Value.Should().BeNull(); @@ -116,8 +116,8 @@ public async Task CatchUnhandledFuncAsync_Exception() public void CatchUnhandledFuncAsync_Exception_Fatal() { var logger = Create>(MockBehavior.Loose); - var bridge = Create(); - var sut = new TopLevelExceptionHandler(logger.Object, bridge.Object); + var eventAggregator = Create(); + var sut = new TopLevelExceptionHandler(logger.Object, eventAggregator.Object); var exception = Assert.ThrowsAsync( async () => await sut.CatchUnhandledAsync(new Func>(() => throw new AppDomainUnloadedException())) diff --git a/DUI3/Speckle.Connectors.DUI/Bindings/OperationProgressManager.cs b/DUI3/Speckle.Connectors.DUI/Bindings/OperationProgressManager.cs index 3724eed44..d5923dc30 100644 --- a/DUI3/Speckle.Connectors.DUI/Bindings/OperationProgressManager.cs +++ b/DUI3/Speckle.Connectors.DUI/Bindings/OperationProgressManager.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Speckle.Connectors.Common.Operations; using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models.Card; diff --git a/DUI3/Speckle.Connectors.DUI/Bindings/TopLevelExceptionHandlerBinding.cs b/DUI3/Speckle.Connectors.DUI/Bindings/TopLevelExceptionHandlerBinding.cs index 617722d69..2fec4a7ca 100644 --- a/DUI3/Speckle.Connectors.DUI/Bindings/TopLevelExceptionHandlerBinding.cs +++ b/DUI3/Speckle.Connectors.DUI/Bindings/TopLevelExceptionHandlerBinding.cs @@ -2,10 +2,6 @@ namespace Speckle.Connectors.DUI.Bindings; -/// -/// Simple binding that can be injected into non- services to get access to the -/// -/// public sealed class TopLevelExceptionHandlerBinding(IBrowserBridge parent) : IBinding { public string Name => "topLevelExceptionHandlerBinding"; diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index f692e41c3..600fae3cf 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Utils; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; @@ -27,7 +28,8 @@ public sealed class BrowserBridge : IBrowserBridge /// private readonly ConcurrentDictionary _resultsStore = new(); - public ITopLevelExceptionHandler TopLevelExceptionHandler { get; } + + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; private readonly IThreadContext _threadContext; private readonly IThreadOptions _threadOptions; @@ -60,18 +62,38 @@ public BrowserBridge( IThreadContext threadContext, IJsonSerializer jsonSerializer, ILogger logger, - ILogger topLogger, IBrowserScriptExecutor browserScriptExecutor, - IThreadOptions threadOptions + IThreadOptions threadOptions, + IEventAggregator eventAggregator, + ITopLevelExceptionHandler topLevelExceptionHandler ) { _threadContext = threadContext; _jsonSerializer = jsonSerializer; _logger = logger; - TopLevelExceptionHandler = new TopLevelExceptionHandler(topLogger, this); // Capture the main thread's SynchronizationContext _browserScriptExecutor = browserScriptExecutor; _threadOptions = threadOptions; + _topLevelExceptionHandler = topLevelExceptionHandler; + eventAggregator + .GetEvent() + .Subscribe( + ex => + { + Send( + BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, + new + { + type = ToastNotificationType.DANGER, + title = "Unhandled Exception Occurred", + description = ex.ToFormattedString(), + autoClose = false + } + ) + .ConfigureAwait(false); + }, + ThreadOption.MainThread + ); } public void AssociateWithBinding(IBinding binding) @@ -110,12 +132,14 @@ public void RunMethod(string methodName, string requestId, string methodArgs) => .RunOnThreadAsync( async () => { - var task = await TopLevelExceptionHandler.CatchUnhandledAsync(async () => - { - var result = await ExecuteMethod(methodName, methodArgs); - string resultJson = _jsonSerializer.Serialize(result); - NotifyUIMethodCallResultReady(requestId, resultJson); - }); + var task = await _topLevelExceptionHandler + .CatchUnhandledAsync(async () => + { + var result = await ExecuteMethod(methodName, methodArgs).ConfigureAwait(false); + string resultJson = _jsonSerializer.Serialize(result); + NotifyUIMethodCallResultReady(requestId, resultJson); + }) + .ConfigureAwait(false); if (task.Exception is not null) { string resultJson = SerializeFormattedException(task.Exception); diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/IBrowserBridge.cs b/DUI3/Speckle.Connectors.DUI/Bridge/IBrowserBridge.cs index 65ec766df..20c85708f 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/IBrowserBridge.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/IBrowserBridge.cs @@ -42,5 +42,4 @@ public Task Send(string eventName, T data, CancellationToken cancellationToke public void Send2(string eventName, T data) where T : class; - public ITopLevelExceptionHandler TopLevelExceptionHandler { get; } } diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index 1684e3c81..2e98ce072 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -1,8 +1,7 @@ using Microsoft.Extensions.Logging; -using Speckle.Connectors.DUI.Bindings; +using Speckle.Connectors.DUI.Eventing; using Speckle.InterfaceGenerator; using Speckle.Sdk; -using Speckle.Sdk.Models.Extensions; namespace Speckle.Connectors.DUI.Bridge; @@ -23,15 +22,15 @@ namespace Speckle.Connectors.DUI.Bridge; public sealed class TopLevelExceptionHandler : ITopLevelExceptionHandler { private readonly ILogger _logger; - public IBrowserBridge Parent { get; } + private readonly IEventAggregator _eventAggregator; public string Name => nameof(TopLevelExceptionHandler); private const string UNHANDLED_LOGGER_TEMPLATE = "An unhandled Exception occured"; - internal TopLevelExceptionHandler(ILogger logger, IBrowserBridge bridge) + public TopLevelExceptionHandler(ILogger logger, IEventAggregator eventAggregator) { _logger = logger; - Parent = bridge; + _eventAggregator = eventAggregator; } /// @@ -41,43 +40,34 @@ internal TopLevelExceptionHandler(ILogger logger, IBro /// The function to invoke and provide error handling for /// will be rethrown, these should be allowed to bubble up to the host app /// - public void CatchUnhandled(Action function) + public Result CatchUnhandled(Action function) { - _ = CatchUnhandled(() => + var r = CatchUnhandled(() => { function(); - return null; + return true; }); + if (r.IsSuccess) + { + return new Result(); + } + return new Result(r.Exception); } /// /// return type /// A result pattern struct (where exceptions have been handled) - public Result CatchUnhandled(Func function) => - CatchUnhandledAsync(() => Task.FromResult(function.Invoke())).Result; //Safe to do a .Result because this as an already completed and non-async Task from the Task.FromResult - - /// - /// A result pattern struct (where exceptions have been handled) - public async Task CatchUnhandledAsync(Func function) + public Result CatchUnhandled(Func function) { try { - try - { - await function(); - return new Result(); - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); - await SetGlobalNotification( - ToastNotificationType.DANGER, - "Unhandled Exception Occured", - ex.ToFormattedString(), - false - ); - return new(ex); - } + return new Result(function()); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + _eventAggregator.GetEvent().Publish(ex); + return new(ex); } catch (Exception ex) { @@ -86,6 +76,22 @@ await SetGlobalNotification( } } + /// + /// A result pattern struct (where exceptions have been handled) + public async Task CatchUnhandledAsync(Func function) + { + var r = await CatchUnhandledAsync(async () => + { + await function(); + return true; + }); + if (r.IsSuccess) + { + return new Result(); + } + return new Result(r.Exception); + } + /// public async Task> CatchUnhandledAsync(Func> function) { @@ -97,7 +103,8 @@ public async Task> CatchUnhandledAsync(Func> function) } catch (Exception ex) when (!ex.IsFatal()) { - await HandleException(ex); + _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + _eventAggregator.GetEvent().Publish(ex); return new(ex); } } @@ -108,32 +115,6 @@ public async Task> CatchUnhandledAsync(Func> function) } } - private async Task HandleException(Exception ex) - { - _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); - - try - { - await SetGlobalNotification( - ToastNotificationType.DANGER, - "Unhandled Exception Occured", - ex.ToFormattedString(), - false - ); - } - catch (Exception toastEx) - { - // Not only was a top level exception caught, but our attempt to display a toast failed! - // Toasts can fail if the BrowserBridge is not yet associated with a binding - // For this reason, binding authors should avoid doing anything in - // the constructors of bindings that may try and use the bridge! - AggregateException aggregateException = - new("An Unhandled top level exception was caught, and the toast failed to display it!", [toastEx, ex]); - - throw aggregateException; - } - } - /// /// Triggers an async action without explicitly needing to await it.
/// Any thrown by invoking will be handled by the
@@ -143,17 +124,5 @@ await SetGlobalNotification( /// In cases where you can use keyword, you should prefer using /// /// - public async void FireAndForget(Func function) => await CatchUnhandledAsync(function); - - private async Task SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose) => - await Parent.Send( - BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: We could move these constants into a DUI3 constants static class - new - { - type, - title, - description = message, - autoClose - } - ); + public async void FireAndForget(Func function) => await CatchUnhandledAsync(function).ConfigureAwait(false); } diff --git a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs index ce594de8a..517b0720d 100644 --- a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs +++ b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs @@ -1,8 +1,10 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.DUI.Models; using Speckle.Sdk; using Speckle.Sdk.Transports; @@ -23,16 +25,28 @@ public static void AddDUI(this IServiceCollectio serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetAssembly(typeof(IdleCallManager))); serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetAssembly(typeof(IServerTransportFactory))); - } + serviceCollection.AddEventsAsTransient(Assembly.GetAssembly(typeof(TDocumentStore))); + serviceCollection.AddEventsAsTransient(Assembly.GetAssembly(typeof(IdleCallManager))); + serviceCollection.AddSingleton(); - public static void RegisterTopLevelExceptionHandler(this IServiceCollection serviceCollection) - { serviceCollection.AddSingleton(sp => sp.GetRequiredService() ); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(c => - c.GetRequiredService().Parent.TopLevelExceptionHandler - ); + serviceCollection.AddSingleton(); + serviceCollection.AddTransient(); + } + + public static IServiceCollection AddEventsAsTransient(this IServiceCollection serviceCollection, Assembly assembly) + { + foreach (var type in assembly.ExportedTypes.Where(t => t.IsNonAbstractClass())) + { + if (type.FindInterfaces((i, _) => i == typeof(ISpeckleEvent), null).Length != 0) + { + serviceCollection.TryAddTransient(type); + } + } + + return serviceCollection; } } diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs new file mode 100644 index 000000000..8601359a9 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs @@ -0,0 +1,97 @@ +using System.Reflection; + +namespace Speckle.Connectors.DUI.Eventing; + +public interface IDelegateReference +{ + /// + /// Gets the referenced object. + /// + /// A instance if the target is valid; otherwise . + Delegate? Target { get; } +} + +public class DelegateReference : IDelegateReference +{ + private readonly Delegate? _delegate; + private readonly WeakReference _weakReference; + private readonly MethodInfo _method; + private readonly Type _delegateType; + + /// + /// Initializes a new instance of . + /// + /// The original to create a reference for. + /// If the class will create a weak reference to the delegate, allowing it to be garbage collected. Otherwise it will keep a strong reference to the target. + /// If the passed is not assignable to . + public DelegateReference(Delegate @delegate, bool keepReferenceAlive) + { + if (@delegate == null) + { + throw new ArgumentNullException(nameof(@delegate)); + } + + if (keepReferenceAlive) + { + _delegate = @delegate; + } + else + { + _weakReference = new WeakReference(@delegate.Target); + _method = @delegate.GetMethodInfo(); + _delegateType = @delegate.GetType(); + } + } + + /// + /// Gets the (the target) referenced by the current object. + /// + /// if the object referenced by the current object has been garbage collected; otherwise, a reference to the referenced by the current object. + public Delegate? Target + { + get + { + if (_delegate != null) + { + return _delegate; + } + else + { + return TryGetDelegate(); + } + } + } + + /// + /// Checks if the (the target) referenced by the current object are equal to another . + /// This is equivalent with comparing with , only more efficient. + /// + /// The other delegate to compare with. + /// True if the target referenced by the current object are equal to . + public bool TargetEquals(Delegate? @delegate) + { + if (_delegate != null) + { + return _delegate == @delegate; + } + if (@delegate == null) + { + return !_method.IsStatic && !_weakReference.IsAlive; + } + return _weakReference.Target == @delegate.Target && Equals(_method, @delegate.GetMethodInfo()); + } + + private Delegate? TryGetDelegate() + { + if (_method.IsStatic) + { + return _method.CreateDelegate(_delegateType, null); + } + object target = _weakReference.Target; + if (target != null) + { + return _method.CreateDelegate(_delegateType, target); + } + return null; + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventAggregator.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventAggregator.cs new file mode 100644 index 000000000..eea2f87ac --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventAggregator.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Speckle.Connectors.DUI.Eventing; + +public interface IEventAggregator +{ + TEventType GetEvent() + where TEventType : EventBase; +} + +//based on Prism.Events at verison 8 +// which was MIT https://github.com/PrismLibrary/Prism/tree/952e343f585b068ccb7d3478d3982485253a0508/src/Prism.Events +// License https://github.com/PrismLibrary/Prism/blob/952e343f585b068ccb7d3478d3982485253a0508/LICENSE +public class EventAggregator(IServiceProvider serviceProvider) : IEventAggregator +{ + private readonly Dictionary _events = new(); + + public TEventType GetEvent() + where TEventType : EventBase + { + lock (_events) + { + if (!_events.TryGetValue(typeof(TEventType), out var existingEvent)) + { + existingEvent = (TEventType)serviceProvider.GetRequiredService(typeof(TEventType)); + _events[typeof(TEventType)] = existingEvent; + } + return (TEventType)existingEvent; + } + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventBase.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventBase.cs new file mode 100644 index 000000000..d0186bbce --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventBase.cs @@ -0,0 +1,122 @@ +namespace Speckle.Connectors.DUI.Eventing; + +/// +/// Defines a base class to publish and subscribe to events. +/// +public abstract class EventBase +{ + private readonly List _subscriptions = new(); + protected ICollection Subscriptions => _subscriptions; + + /// + /// Adds the specified to the subscribers' collection. + /// + /// The subscriber. + /// The that uniquely identifies every subscriber. + /// + /// Adds the subscription to the internal list and assigns it a new . + /// + protected virtual SubscriptionToken InternalSubscribe(IEventSubscription eventSubscription) + { + if (eventSubscription == null) + { + throw new ArgumentNullException(nameof(eventSubscription)); + } + + eventSubscription.SubscriptionToken = new SubscriptionToken(Unsubscribe); + + lock (Subscriptions) + { + Subscriptions.Add(eventSubscription); + } + return eventSubscription.SubscriptionToken; + } + + /// + /// Calls all the execution strategies exposed by the list of . + /// + /// The arguments that will be passed to the listeners. + /// Before executing the strategies, this class will prune all the subscribers from the + /// list that return a when calling the + /// method. + protected virtual void InternalPublish(params object[] arguments) + { + List> executionStrategies = PruneAndReturnStrategies(); + foreach (var executionStrategy in executionStrategies) + { + executionStrategy(arguments); + } + } + + /// + /// Removes the subscriber matching the . + /// + /// The returned by while subscribing to the event. + public virtual void Unsubscribe(SubscriptionToken token) + { + lock (Subscriptions) + { + IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token); + if (subscription != null) + { + Subscriptions.Remove(subscription); + } + } + } + + /// + /// Returns if there is a subscriber matching . + /// + /// The returned by while subscribing to the event. + /// if there is a that matches; otherwise . + public virtual bool Contains(SubscriptionToken token) + { + lock (Subscriptions) + { + IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token); + return subscription != null; + } + } + + private List> PruneAndReturnStrategies() + { + List> returnList = new(); + + lock (Subscriptions) + { + for (var i = Subscriptions.Count - 1; i >= 0; i--) + { + Action? listItem = _subscriptions[i].GetExecutionStrategy(); + + if (listItem == null) + { + // Prune from main list. Log? + _subscriptions.RemoveAt(i); + } + else + { + returnList.Add(listItem); + } + } + } + + return returnList; + } + + /// + /// Forces the PubSubEvent to remove any subscriptions that no longer have an execution strategy. + /// + public void Prune() + { + lock (Subscriptions) + { + for (var i = Subscriptions.Count - 1; i >= 0; i--) + { + if (_subscriptions[i].GetExecutionStrategy() == null) + { + _subscriptions.RemoveAt(i); + } + } + } + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs new file mode 100644 index 000000000..25543e4cd --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs @@ -0,0 +1,139 @@ +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public interface IEventSubscription +{ + /// + /// Gets or sets a that identifies this . + /// + /// A token that identifies this . + SubscriptionToken SubscriptionToken { get; set; } + + /// + /// Gets the execution strategy to publish this event. + /// + /// An with the execution strategy, or if the is no longer valid. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Action? GetExecutionStrategy(); +} + +/// +/// Provides a way to retrieve a to execute an action depending +/// on the value of a second filter predicate that returns true if the action should execute. +/// +/// The type to use for the generic and types. +public class EventSubscription : IEventSubscription +{ + private readonly IDelegateReference _actionReference; + private readonly IDelegateReference _filterReference; + private readonly ITopLevelExceptionHandler _exceptionHandler; + + /// + /// Creates a new instance of . + /// + ///A reference to a delegate of type . + ///A reference to a delegate of type . + ///When or are . + ///When the target of is not of type , + ///or the target of is not of type . + public EventSubscription( + IDelegateReference actionReference, + IDelegateReference filterReference, + ITopLevelExceptionHandler exceptionHandler + ) + { + if (actionReference == null) + { + throw new ArgumentNullException(nameof(actionReference)); + } + + if (actionReference.Target is not Action) + { + throw new ArgumentException(null, nameof(actionReference)); + } + + if (filterReference == null) + { + throw new ArgumentNullException(nameof(filterReference)); + } + + if (filterReference.Target is not Predicate) + { + throw new ArgumentException(null, nameof(filterReference)); + } + + _actionReference = actionReference; + _filterReference = filterReference; + _exceptionHandler = exceptionHandler; + } + + /// + /// Gets the target that is referenced by the . + /// + /// An or if the referenced target is not alive. + public Action? Action => (Action?)_actionReference.Target; + + /// + /// Gets the target that is referenced by the . + /// + /// An or if the referenced target is not alive. + public Predicate? Filter => (Predicate?)_filterReference.Target; + + /// + /// Gets or sets a that identifies this . + /// + /// A token that identifies this . + public SubscriptionToken SubscriptionToken { get; set; } + + /// + /// Gets the execution strategy to publish this event. + /// + /// An with the execution strategy, or if the is no longer valid. + /// + /// If or are no longer valid because they were + /// garbage collected, this method will return . + /// Otherwise it will return a delegate that evaluates the and if it + /// returns will then call . The returned + /// delegate holds hard references to the and target + /// delegates. As long as the returned delegate is not garbage collected, + /// the and references delegates won't get collected either. + /// + public virtual Action? GetExecutionStrategy() + { + Action? action = Action; + if (action is null) + { + return null; + } + Predicate? filter = Filter; + return arguments => + { + TPayload argument = (TPayload)arguments[0]; + if (filter is null) + { + InvokeAction(action, argument); + } + else if (filter(argument)) + { + InvokeAction(action, argument); + } + }; + } + + /// + /// Invokes the specified synchronously when not overridden. + /// + /// The action to execute. + /// The payload to pass while invoking it. + /// An is thrown if is null. + public virtual void InvokeAction(Action action, TPayload argument) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + _exceptionHandler.CatchUnhandled(() => action(argument)); + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs new file mode 100644 index 000000000..61354309f --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs @@ -0,0 +1,13 @@ +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public class ExceptionEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DocumentChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class IdleEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : OneTimeThreadedEvent(threadContext, exceptionHandler); diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/ISpeckleEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/ISpeckleEvent.cs new file mode 100644 index 000000000..0d8e00cc1 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/ISpeckleEvent.cs @@ -0,0 +1,6 @@ +namespace Speckle.Connectors.DUI.Eventing; + +public interface ISpeckleEvent +{ + string Name { get; } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/MainThreadEventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/MainThreadEventSubscription.cs new file mode 100644 index 000000000..c524f027f --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/MainThreadEventSubscription.cs @@ -0,0 +1,16 @@ +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public class MainThreadEventSubscription( + IDelegateReference actionReference, + IDelegateReference filterReference, + IThreadContext threadContext, + ITopLevelExceptionHandler exceptionHandler, + bool isOnce +) : OneTimeEventSubscription(actionReference, filterReference, exceptionHandler, isOnce) +{ + public override void InvokeAction(Action action, T payload) => + threadContext.RunOnMain(() => action.Invoke(payload)); +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeEventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeEventSubscription.cs new file mode 100644 index 000000000..f58e102e6 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeEventSubscription.cs @@ -0,0 +1,20 @@ +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public class OneTimeEventSubscription( + IDelegateReference actionReference, + IDelegateReference filterReference, + ITopLevelExceptionHandler exceptionHandler, + bool isOnce +) : EventSubscription(actionReference, filterReference, exceptionHandler) +{ + public override void InvokeAction(Action action, T payload) + { + action.Invoke(payload); + if (isOnce) + { + SubscriptionToken.Dispose(); + } + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs new file mode 100644 index 000000000..db3270e79 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs @@ -0,0 +1,79 @@ +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public abstract class OneTimeThreadedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler) + where T : notnull +{ + private readonly Dictionary _activeTokens = new(); + + public SubscriptionToken OneTimeSubscribe( + string id, + Func action, + ThreadOption threadOption = ThreadOption.PublisherThread, + bool keepSubscriberReferenceAlive = false, + Predicate? filter = null + ) + { + return OneTimeInternal(id, t => action(t), threadOption, keepSubscriberReferenceAlive, filter); + } + + public SubscriptionToken OneTimeSubscribe( + string id, + Func action, + ThreadOption threadOption = ThreadOption.PublisherThread, + bool keepSubscriberReferenceAlive = false, + Predicate? filter = null + ) + { + return OneTimeInternal(id, _ => action(), threadOption, keepSubscriberReferenceAlive, filter); + } + + public SubscriptionToken OneTimeSubscribe( + string id, + Action action, + ThreadOption threadOption = ThreadOption.PublisherThread, + bool keepSubscriberReferenceAlive = false, + Predicate? filter = null + ) + { + return OneTimeInternal(id, action, threadOption, keepSubscriberReferenceAlive, filter); + } + + public SubscriptionToken OneTimeSubscribe( + string id, + Action action, + ThreadOption threadOption = ThreadOption.PublisherThread, + bool keepSubscriberReferenceAlive = false, + Predicate? filter = null + ) + { + return OneTimeInternal(id, _ => action(), threadOption, keepSubscriberReferenceAlive, filter); + } + + private SubscriptionToken OneTimeInternal( + string id, + Action action, + ThreadOption threadOption, + bool keepSubscriberReferenceAlive, + Predicate? filter + ) + { + lock (_activeTokens) + { + if (_activeTokens.TryGetValue(id, out var token)) + { + if (token.IsActive) + { + return token; + } + _activeTokens.Remove(id); + } + token = SubscribeOnceOrNot(action, threadOption, keepSubscriberReferenceAlive, filter, true); + _activeTokens.Add(id, token); + return token; + } + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/PubSubEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/PubSubEvent.cs new file mode 100644 index 000000000..929015432 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/PubSubEvent.cs @@ -0,0 +1,138 @@ +namespace Speckle.Connectors.DUI.Eventing; + +/// +/// Defines a class that manages publication and subscription to events. +/// +/// The type of message that will be passed to the subscribers. +public abstract class PubSubEvent : EventBase + where TPayload : notnull +{ + /// + /// Subscribes a delegate to an event that will be published on the . + /// will maintain a to the target of the supplied delegate. + /// + /// The delegate that gets executed when the event is published. + /// A that uniquely identifies the added subscription. + /// + /// The PubSubEvent collection is thread-safe. + /// + public SubscriptionToken Subscribe(Action action) => Subscribe(action, ThreadOption.PublisherThread); + + /// + /// Subscribes a delegate to an event that will be published on the + /// + /// The delegate that gets executed when the event is raised. + /// Filter to evaluate if the subscriber should receive the event. + /// A that uniquely identifies the added subscription. + public virtual SubscriptionToken Subscribe(Action action, Predicate filter) => + Subscribe(action, ThreadOption.PublisherThread, false, filter); + + /// + /// Subscribes a delegate to an event. + /// PubSubEvent will maintain a to the Target of the supplied delegate. + /// + /// The delegate that gets executed when the event is raised. + /// Specifies on which thread to receive the delegate callback. + /// A that uniquely identifies the added subscription. + /// + /// The PubSubEvent collection is thread-safe. + /// + public SubscriptionToken Subscribe(Action action, ThreadOption threadOption) => + Subscribe(action, threadOption, false); + + /// + /// Subscribes a delegate to an event that will be published on the . + /// + /// The delegate that gets executed when the event is published. + /// When , the keeps a reference to the subscriber so it does not get garbage collected. + /// A that uniquely identifies the added subscription. + /// + /// If is set to , will maintain a to the Target of the supplied delegate. + /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior. + /// + /// The PubSubEvent collection is thread-safe. + /// + public SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive) => + Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive); + + /// + /// Subscribes a delegate to an event. + /// + /// The delegate that gets executed when the event is published. + /// Specifies on which thread to receive the delegate callback. + /// When , the keeps a reference to the subscriber so it does not get garbage collected. + /// A that uniquely identifies the added subscription. + /// + /// If is set to , will maintain a to the Target of the supplied delegate. + /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior. + /// + /// The PubSubEvent collection is thread-safe. + /// + public SubscriptionToken Subscribe( + Action action, + ThreadOption threadOption, + bool keepSubscriberReferenceAlive + ) => Subscribe(action, threadOption, keepSubscriberReferenceAlive, null); + + /// + /// Subscribes a delegate to an event. + /// + /// The delegate that gets executed when the event is published. + /// Specifies on which thread to receive the delegate callback. + /// When , the keeps a reference to the subscriber so it does not get garbage collected. + /// Filter to evaluate if the subscriber should receive the event. + /// A that uniquely identifies the added subscription. + /// + /// If is set to , will maintain a to the Target of the supplied delegate. + /// If not using a WeakReference ( is ), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior. + /// + /// The PubSubEvent collection is thread-safe. + /// + public abstract SubscriptionToken Subscribe( + Action action, + ThreadOption threadOption, + bool keepSubscriberReferenceAlive, + Predicate? filter + ); + + /// + /// Publishes the . + /// + /// Message to pass to the subscribers. + public virtual void Publish(TPayload payload) => InternalPublish(payload); + + /// + /// Removes the first subscriber matching from the subscribers' list. + /// + /// The used when subscribing to the event. + public virtual void Unsubscribe(Action subscriber) + { + lock (Subscriptions) + { + IEventSubscription eventSubscription = Subscriptions + .Cast>() + .FirstOrDefault(evt => evt.Action == subscriber); + if (eventSubscription != null) + { + Subscriptions.Remove(eventSubscription); + } + } + } + + /// + /// Returns if there is a subscriber matching . + /// + /// The used when subscribing to the event. + /// if there is an that matches; otherwise . + public virtual bool Contains(Action subscriber) + { + IEventSubscription eventSubscription; + lock (Subscriptions) + { + eventSubscription = Subscriptions + .Cast>() + .FirstOrDefault(evt => evt.Action == subscriber); + } + return eventSubscription != null; + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/SubscriptionToken.cs b/DUI3/Speckle.Connectors.DUI/Eventing/SubscriptionToken.cs new file mode 100644 index 000000000..64f92b127 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/SubscriptionToken.cs @@ -0,0 +1,48 @@ +namespace Speckle.Connectors.DUI.Eventing; + +//based on Prism.Events +public sealed class SubscriptionToken(Action unsubscribeAction) + : IEquatable, + IDisposable +{ + private readonly Guid _token = Guid.NewGuid(); + private Action? _unsubscribeAction = unsubscribeAction; + + public bool Equals(SubscriptionToken? other) + { + if (other == null) + { + return false; + } + + return Equals(_token, other._token); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + return Equals(obj as SubscriptionToken); + } + + public override int GetHashCode() => _token.GetHashCode(); + + public bool IsActive => _unsubscribeAction != null; + + public void Dispose() + { + // While the SubscriptionToken class implements IDisposable, in the case of weak subscriptions + // (i.e. keepSubscriberReferenceAlive set to false in the Subscribe method) it's not necessary to unsubscribe, + // as no resources should be kept alive by the event subscription. + // In such cases, if a warning is issued, it could be suppressed. + + if (_unsubscribeAction != null) + { + _unsubscribeAction(this); + _unsubscribeAction = null; + } + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/ThreadOption.cs b/DUI3/Speckle.Connectors.DUI/Eventing/ThreadOption.cs new file mode 100644 index 000000000..6866be71e --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/ThreadOption.cs @@ -0,0 +1,8 @@ +namespace Speckle.Connectors.DUI.Eventing; + +public enum ThreadOption +{ + PublisherThread, + MainThread, + WorkerThread +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs new file mode 100644 index 000000000..30d8ed2de --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs @@ -0,0 +1,87 @@ +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public abstract class ThreadedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : PubSubEvent, + ISpeckleEvent + where T : notnull +{ + public string Name { get; } = typeof(T).Name; + + public SubscriptionToken Subscribe( + Func action, + ThreadOption threadOption, + bool keepSubscriberReferenceAlive, + Predicate? filter + ) + { + return SubscribeOnceOrNot(t => action(t), threadOption, keepSubscriberReferenceAlive, filter, false); + } + + public override SubscriptionToken Subscribe( + Action action, + ThreadOption threadOption, + bool keepSubscriberReferenceAlive, + Predicate? filter + ) + { + return SubscribeOnceOrNot(action, threadOption, keepSubscriberReferenceAlive, filter, false); + } + + protected SubscriptionToken SubscribeOnceOrNot( + Action action, + ThreadOption threadOption, + bool keepSubscriberReferenceAlive, + Predicate? filter, + bool isOnce + ) + { + IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive); + IDelegateReference filterReference; + if (filter != null) + { + filterReference = new DelegateReference(filter, keepSubscriberReferenceAlive); + } + else + { + filterReference = new DelegateReference( + new Predicate( + delegate + { + return true; + } + ), + true + ); + } + EventSubscription subscription; + switch (threadOption) + { + case ThreadOption.WorkerThread: + subscription = new WorkerEventSubscription( + actionReference, + filterReference, + threadContext, + exceptionHandler, + isOnce + ); + break; + case ThreadOption.MainThread: + subscription = new MainThreadEventSubscription( + actionReference, + filterReference, + threadContext, + exceptionHandler, + isOnce + ); + break; + case ThreadOption.PublisherThread: + default: + subscription = new OneTimeEventSubscription(actionReference, filterReference, exceptionHandler, isOnce); + break; + } + return InternalSubscribe(subscription); + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/WorkerEventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/WorkerEventSubscription.cs new file mode 100644 index 000000000..1083987be --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/WorkerEventSubscription.cs @@ -0,0 +1,16 @@ +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; + +namespace Speckle.Connectors.DUI.Eventing; + +public class WorkerEventSubscription( + IDelegateReference actionReference, + IDelegateReference filterReference, + IThreadContext threadContext, + ITopLevelExceptionHandler exceptionHandler, + bool isOnce +) : OneTimeEventSubscription(actionReference, filterReference, exceptionHandler, isOnce) +{ + public override void InvokeAction(Action action, TPayload argument) => + threadContext.RunOnWorker(() => action(argument)); +} diff --git a/DUI3/Speckle.Connectors.DUI/Models/DocumentModelStore.cs b/DUI3/Speckle.Connectors.DUI/Models/DocumentModelStore.cs index 62c4a7984..9b6622c50 100644 --- a/DUI3/Speckle.Connectors.DUI/Models/DocumentModelStore.cs +++ b/DUI3/Speckle.Connectors.DUI/Models/DocumentModelStore.cs @@ -13,12 +13,6 @@ public abstract class DocumentModelStore(IJsonSerializer serializer) { private readonly List _models = new(); - /// - /// This event is triggered by each specific host app implementation of the document model store. - /// - // POC: unsure about the PublicAPI annotation, unsure if this changed handle should live here on the store... :/ - public event EventHandler? DocumentChanged; - //needed for javascript UI public IReadOnlyList Models { @@ -88,8 +82,6 @@ public void RemoveModel(ModelCard model) } } - protected void OnDocumentChanged() => DocumentChanged?.Invoke(this, EventArgs.Empty); - public IEnumerable GetSenders() { lock (_models) diff --git a/Sdk/Speckle.Connectors.Common/Builders/IRootObjectBuilder.cs b/Sdk/Speckle.Connectors.Common/Builders/IRootObjectBuilder.cs index f1d9acff4..1a728fb44 100644 --- a/Sdk/Speckle.Connectors.Common/Builders/IRootObjectBuilder.cs +++ b/Sdk/Speckle.Connectors.Common/Builders/IRootObjectBuilder.cs @@ -1,4 +1,4 @@ -using Speckle.Connectors.Common.Conversion; +using Speckle.Connectors.Common.Conversion; using Speckle.Connectors.Common.Operations; using Speckle.Sdk.Models; diff --git a/Sdk/Speckle.Connectors.Common/Operations/SendOperation.cs b/Sdk/Speckle.Connectors.Common/Operations/SendOperation.cs index c131d0058..357e82566 100644 --- a/Sdk/Speckle.Connectors.Common/Operations/SendOperation.cs +++ b/Sdk/Speckle.Connectors.Common/Operations/SendOperation.cs @@ -48,7 +48,7 @@ public async Task Execute( return new(rootObjId, convertedReferences, buildResult.ConversionResults); } - public async Task Send( + private async Task Send( Base commitObject, SendInfo sendInfo, IProgress onOperationProgressed, diff --git a/Sdk/Speckle.Connectors.Common/Threading/DefaultThreadContext.cs b/Sdk/Speckle.Connectors.Common/Threading/DefaultThreadContext.cs index 1a56531ec..92c22902e 100644 --- a/Sdk/Speckle.Connectors.Common/Threading/DefaultThreadContext.cs +++ b/Sdk/Speckle.Connectors.Common/Threading/DefaultThreadContext.cs @@ -1,4 +1,4 @@ -namespace Speckle.Connectors.Common.Threading; +namespace Speckle.Connectors.Common.Threading; public class DefaultThreadContext : ThreadContext { diff --git a/Sdk/Speckle.Connectors.Common/Threading/ThreadContextExtensions.cs b/Sdk/Speckle.Connectors.Common/Threading/ThreadContextExtensions.cs index b6dbf2c59..d0cb56029 100644 --- a/Sdk/Speckle.Connectors.Common/Threading/ThreadContextExtensions.cs +++ b/Sdk/Speckle.Connectors.Common/Threading/ThreadContextExtensions.cs @@ -1,4 +1,4 @@ -namespace Speckle.Connectors.Common.Threading; +namespace Speckle.Connectors.Common.Threading; public static class ThreadContextExtensions { diff --git a/Speckle.Connectors.sln b/Speckle.Connectors.sln index a28a14618..2e78ba884 100644 --- a/Speckle.Connectors.sln +++ b/Speckle.Connectors.sln @@ -15,8 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{85A13E README.md = README.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DUI", "DUI", "{FD4D6594-D81E-456F-8F2E-35B09E04A755}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Revit", "Revit", "{D92751C8-1039-4005-90B2-913E55E0B8BD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{2E00592E-558D-492D-88F9-3ECEE4C0C7DA}" @@ -658,7 +656,7 @@ Global {DC570FFF-6FE5-47BD-8BC1-B471A6067786} = {FC224610-32D3-454E-9BC1-1219FE8ACD5F} {E1C43415-3200-45F4-8BF9-A4DD7D7F2ED6} = {FC224610-32D3-454E-9BC1-1219FE8ACD5F} {26391930-F86F-47E0-A5F6-B89919E38CE1} = {E9DEBA00-50A4-485D-BA65-D8AB3E3467AB} - {D81C0B87-F0C1-4297-A147-02F001FB7E1E} = {FD4D6594-D81E-456F-8F2E-35B09E04A755} + {D81C0B87-F0C1-4297-A147-02F001FB7E1E} = {2E00592E-558D-492D-88F9-3ECEE4C0C7DA} {7291B93C-615D-42DE-B8C1-3F9DF643E0FC} = {2E00592E-558D-492D-88F9-3ECEE4C0C7DA} {8AEF06C0-CA5C-4460-BC2D-ADE5F35D0434} = {2E00592E-558D-492D-88F9-3ECEE4C0C7DA} {9584AEE5-CD59-46E6-93E6-2DC2B5285B75} = {42826721-9A18-4762-8BA9-F1429DD5C5B1} @@ -672,7 +670,7 @@ Global {804E065F-914C-414A-AF84-009312C3CFF6} = {42826721-9A18-4762-8BA9-F1429DD5C5B1} {9ADD1B7A-6401-4202-8613-F668E2FBC0A4} = {4721AA15-AF6E-4A62-A2C3-65564DC563E6} {631C295A-7CCF-4B42-8686-7034E31469E7} = {2D5AE63D-85C0-43D1-84BF-04418ED93F63} - {7420652C-3046-4F38-BE64-9B9E69D76FA2} = {FD4D6594-D81E-456F-8F2E-35B09E04A755} + {7420652C-3046-4F38-BE64-9B9E69D76FA2} = {2E00592E-558D-492D-88F9-3ECEE4C0C7DA} {C50AA3E3-8C31-4131-9DEC-1D8B377D5A89} = {59E8E8F3-4E42-4E92-83B3-B1C2AB901D18} {CA8EAE01-AB9F-4EC1-B6F3-73721487E9E1} = {2F45036E-D817-41E9-B82F-DBE013EC95D0} {35175682-DA83-4C0A-A49D-B191F5885D8E} = {4721AA15-AF6E-4A62-A2C3-65564DC563E6} @@ -699,7 +697,7 @@ Global {7F1FDCF2-0CE8-4119-B3C1-F2CC6D7E1C36} = {0AF38BA3-65A0-481B-8CBB-B82E406E1575} {19424B55-058C-4E9C-B86F-700AEF9EAEC3} = {0AF38BA3-65A0-481B-8CBB-B82E406E1575} {0AF38BA3-65A0-481B-8CBB-B82E406E1575} = {D92751C8-1039-4005-90B2-913E55E0B8BD} - {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885} = {FD4D6594-D81E-456F-8F2E-35B09E04A755} + {EB83A3A3-F9B6-4281-8EBF-F7289FB5D885} = {2E00592E-558D-492D-88F9-3ECEE4C0C7DA} {D8069A23-AD2E-4C9E-8574-7E8C45296A46} = {0AF38BA3-65A0-481B-8CBB-B82E406E1575} {2D5AE63D-85C0-43D1-84BF-04418ED93F63} = {804E065F-914C-414A-AF84-009312C3CFF6} {2F45036E-D817-41E9-B82F-DBE013EC95D0} = {804E065F-914C-414A-AF84-009312C3CFF6} @@ -789,6 +787,7 @@ Global Converters\Civil3d\Speckle.Converters.Civil3dShared\Speckle.Converters.Civil3dShared.projitems*{25172c49-7aa4-4739-bb07-69785094c379}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{26391930-f86f-47e0-a5f6-b89919e38ce1}*SharedItemsImports = 5 Converters\Civil3d\Speckle.Converters.Civil3dShared\Speckle.Converters.Civil3dShared.projitems*{35175682-da83-4c0a-a49d-b191f5885d8e}*SharedItemsImports = 13 + Converters\CSi\Speckle.Converters.ETABSShared\Speckle.Converters.ETABSShared.projitems*{36377858-d696-4567-ab05-637f4ec841f5}*SharedItemsImports = 13 Connectors\Tekla\Speckle.Connector.TeklaShared\Speckle.Connectors.TeklaShared.projitems*{3ab9028b-b2d2-464b-9ba3-39c192441e50}*SharedItemsImports = 13 Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{41bc679f-887f-44cf-971d-a5502ee87db0}*SharedItemsImports = 13 Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{4459f2b1-a340-488e-a856-eb2ae9c72ad4}*SharedItemsImports = 5