From 9a8724d74e1183dc5d71da405263c5c0c4f0db2b Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 08:49:22 +0000 Subject: [PATCH 01/12] This is a workaround for Revit's order of operations when initializing --- .../Plugin/RevitCefPlugin.cs | 7 ++++++- .../Plugin/RevitExternalApplication.cs | 2 +- DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs | 8 ++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs index 30b5bb5a3..fb23025c7 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs @@ -11,6 +11,7 @@ using Speckle.Connectors.Common; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Models; using Speckle.Converters.RevitShared.Helpers; using Speckle.Sdk; @@ -99,13 +100,17 @@ private void CreateTabAndRibbonPanel(UIControlledApplication application) dui3Button.SetContextualHelp(new ContextualHelp(ContextualHelpType.Url, "https://speckle.systems")); } - private void OnApplicationInitialized(object? sender, Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e) + private async void OnApplicationInitialized( + object? sender, + Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e + ) { var uiApplication = new UIApplication(sender as Application); _revitContext.UIApplication = uiApplication; // POC: might be worth to interface this out, we shall see... RevitTask.Initialize(uiApplication); + await _serviceProvider.GetRequiredService().OnDocumentStoreInitialized(); PostApplicationInit(); // for double-click file open } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs index 0cd5c1b60..05ee7ddb8 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs @@ -48,7 +48,7 @@ public Result OnStartup(UIControlledApplication application) services.AddRevitConverters(); services.AddSingleton(application); _container = services.BuildServiceProvider(); - _container.UseDUI(); + _container.UseDUI(true); // resolve root object _revitPlugin = _container.GetRequiredService(); diff --git a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs index dbda73c28..8729fdba6 100644 --- a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs +++ b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs @@ -51,7 +51,7 @@ public static IServiceCollection AddEventsAsTransient(this IServiceCollection se return serviceCollection; } - public static IServiceProvider UseDUI(this IServiceProvider serviceProvider) + public static IServiceProvider UseDUI(this IServiceProvider serviceProvider, bool isRevit = false) { //observe the unobserved! TaskScheduler.UnobservedTaskException += async (_, args) => @@ -64,7 +64,11 @@ await serviceProvider args.SetObserved(); }; - serviceProvider.GetRequiredService().OnDocumentStoreInitialized().Wait(); + if (!isRevit) + { + serviceProvider.GetRequiredService().OnDocumentStoreInitialized().Wait(); + } + return serviceProvider; } } From 0d14be1625953d2331ef7d5205a17f11fbb7b64b Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 09:14:50 +0000 Subject: [PATCH 02/12] Fix event listening --- .../Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs index 9e9a27fa2..4233ebd65 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs @@ -120,7 +120,7 @@ private void SubscribeToRhinoEvents(IEventAggregator eventAggregator) // NOTE: BE CAREFUL handling things in this event handler since it is triggered whenever we save something into file! eventAggregator - .GetEvent() + .GetEvent() .Subscribe(async e => { var newUnit = e.Document.ModelUnitSystem; From c4ab2db51d9d057e67116e57c3377d9348feb6bd Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 09:40:16 +0000 Subject: [PATCH 03/12] Only allow methods on classes as opposed to anonymous lambdas --- .../Bindings/ArcGISSendBinding.cs | 9 +- .../Bindings/BasicConnectorBinding.cs | 5 +- .../Utils/ArcGisDocumentStore.cs | 4 +- .../Bindings/AutocadBasicConnectorBinding.cs | 8 +- .../Bindings/AutocadSendBaseBinding.cs | 3 +- .../HostApp/AutocadDocumentModelStore.cs | 2 +- .../Bindings/BasicConnectorBindingRevit.cs | 9 +- .../Bindings/RevitSendBinding.cs | 8 +- .../HostApp/RevitDocumentStore.cs | 4 +- .../Bindings/RhinoBasicConnectorBinding.cs | 17 +- .../Bindings/RhinoSelectionBinding.cs | 4 +- .../Bindings/RhinoSendBinding.cs | 323 ++++++++++-------- .../Speckle.Connectors.RhinoShared/Events.cs | 6 + .../HostApp/RhinoDocumentStore.cs | 36 +- .../Bindings/TeklaBasicConnectorBinding.cs | 8 +- .../Bindings/TeklaSelectionBinding.cs | 4 +- .../HostApp/TeklaDocumentModelStore.cs | 16 +- .../Bridge/BrowserBridge.cs | 29 +- .../Eventing/DelegateReference.cs | 19 +- .../Eventing/EventSubscription.cs | 10 +- .../Speckle.Connectors.DUI/Eventing/Events.cs | 2 +- .../Eventing/SpeckleEvent.cs | 29 +- .../Eventing/WeakOrStrongReference.cs | 24 -- 23 files changed, 300 insertions(+), 279 deletions(-) delete mode 100644 DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index ea887b66f..def6e6188 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -91,13 +91,12 @@ ITopLevelExceptionHandler topLevelExceptionHandler Commands = new SendBindingUICommands(parent); SubscribeToArcGISEvents(); eventAggregator - .GetEvent() - .Subscribe(_ => - { - _sendConversionCache.ClearCache(); - }); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); } + private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache(); + private void SubscribeToArcGISEvents() { LayersRemovedEvent.Subscribe( diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index 49b51a384..57572a42c 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -35,10 +35,11 @@ IEventAggregator eventAggregator Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() - .Subscribe(async _ => await Commands.NotifyDocumentChanged().ConfigureAwait(false)); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); public string GetSourceApplicationName() => _speckleApplication.Slug; public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs index e64953be5..1669f0520 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs @@ -51,7 +51,7 @@ public override async Task OnDocumentStoreInitialized() { IsDocumentInit = true; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } } @@ -85,7 +85,7 @@ private async void OnMapViewChanged(ActiveMapViewChangedEventArgs args) IsDocumentInit = true; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(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 8053a6e60..b9e342c44 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -42,15 +42,13 @@ IThreadContext threadContext _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - }); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); _logger = logger; _threadContext = threadContext; } + private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs index 7c3a7e576..f27df5f4f 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs @@ -90,9 +90,10 @@ IEventAggregator eventAggregator } // Since ids of the objects generates from same seed, we should clear the cache always whenever doc swapped. - eventAggregator.GetEvent().Subscribe(_ => _sendConversionCache.ClearCache()); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache(); private readonly List _docSubsTracker = new(); private void SubscribeToObjectChanges(Document doc) diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs index 899954633..42e6e61c6 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs @@ -52,7 +52,7 @@ private async void OnDocChangeInternal(Document? doc) _previousDocName = currentDocName; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } protected override void LoadState() diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index 9f8d681e9..b5cef82c6 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -38,14 +38,11 @@ IEventAggregator eventAggregator _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - // POC: event binding? eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - }); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index 17b4901e3..f45aefaa4 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -84,12 +84,10 @@ ITopLevelExceptionHandler topLevelExceptionHandler revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) => topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e)); eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await OnDocumentChanged().ConfigureAwait(false); - }); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); public List GetSendFilters() => [ diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index f1b8d5e89..6647f8b63 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -56,7 +56,7 @@ ITopLevelExceptionHandler topLevelExceptionHandler } public override Task OnDocumentStoreInitialized() => - _eventAggregator.GetEvent().PublishAsync(new object()); + _eventAggregator.GetEvent().PublishAsync(new object()); /// /// This is the place where we track document switch for new document -> Responsible to Read from new doc @@ -80,7 +80,7 @@ private void OnViewActivated(object? _, ViewActivatedEventArgs e) async () => { LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } ); } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs index 1cdc94ed1..336c87843 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs @@ -38,15 +38,18 @@ IEventAggregator eventAggregator Commands = new BasicConnectorBindingCommands(parent); 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. - _sendConversionCache.ClearCache(); - }); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) + { + await Commands.NotifyDocumentChanged(); + // Note: this prevents scaling issues when copy-pasting from one rhino doc to another in the same session. + _sendConversionCache.ClearCache(); + } + + public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs index f91a37ba3..87dad466f 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs @@ -25,9 +25,9 @@ public RhinoSelectionBinding(IBrowserBridge parent, IEventAggregator eventAggreg } private void OnSelectionChange(EventArgs eventArgs) => - _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSelectionBinding), _ => UpdateSelection()); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSelectionBinding), UpdateSelection); - private void UpdateSelection() + private void UpdateSelection(object _) { SelectionInfo selInfo = GetSelection(); Parent.Send(SELECTION_EVENT, selInfo); diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs index 4233ebd65..f2df9ea38 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs @@ -42,6 +42,7 @@ public sealed class RhinoSendBinding : ISendBinding private readonly IRhinoConversionSettingsFactory _rhinoConversionSettingsFactory; private readonly ISpeckleApplication _speckleApplication; private readonly ISdkActivityFactory _activityFactory; + private readonly IEventAggregator _eventAggregator; /// /// Used internally to aggregate the changed objects' id. Objects in this list will be reconverted. @@ -88,194 +89,218 @@ IEventAggregator eventAggregator 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; + _eventAggregator = eventAggregator; PreviousUnitSystem = RhinoDoc.ActiveDoc.ModelUnitSystem; SubscribeToRhinoEvents(eventAggregator); } private void SubscribeToRhinoEvents(IEventAggregator eventAggregator) { - Command.BeginCommand += (_, e) => - { - if (e.CommandEnglishName == "BlockEdit") - { - var selectedObject = RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false).First(); - ChangedObjectIds[selectedObject.Id.ToString()] = 1; - } - - if (e.CommandEnglishName == "Ungroup") - { - foreach (RhinoObject selectedObject in RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false)) - { - ChangedObjectIdsInGroupsOrLayers[selectedObject.Id.ToString()] = 1; - } - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }; + eventAggregator + .GetEvent() + .Subscribe(OnBeginCommandEvent); + eventAggregator .GetEvent() - .Subscribe(e => - { - PreviousUnitSystem = e.Document.ModelUnitSystem; - }); + .Subscribe(OnActiveDocumentChanged); // NOTE: BE CAREFUL handling things in this event handler since it is triggered whenever we save something into file! eventAggregator .GetEvent() - .Subscribe(async e => - { - var newUnit = e.Document.ModelUnitSystem; - if (newUnit != PreviousUnitSystem) - { - PreviousUnitSystem = newUnit; - - await InvalidateAllSender(); - } - }); + .Subscribe(OnDocumentPropertiesChanged); eventAggregator .GetEvent() - .Subscribe(e => - { - if (!_store.IsDocumentInit) - { - return; - } - - ChangedObjectIds[e.ObjectId.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + .Subscribe(OnAddRhinoObject); eventAggregator .GetEvent() - .Subscribe(e => - { - if (!_store.IsDocumentInit) - { - return; - } - - ChangedObjectIds[e.ObjectId.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + .Subscribe(OnDeleteRhinoObject); // 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)! eventAggregator .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } - - if (args is RhinoDoc.RenderMaterialAssignmentChangedEventArgs changedEventArgs) - { - ChangedObjectIds[changedEventArgs.ObjectId.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }); + .Subscribe(OnRenderMaterialsTableEvent); eventAggregator .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } - - foreach (var obj in RhinoDoc.ActiveDoc.Groups.GroupMembers(args.GroupIndex)) - { - ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; - } - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + .Subscribe(OnGroupTableEvent + ); eventAggregator .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } - - if ( - args.EventType == LayerTableEventType.Deleted - || args.EventType == LayerTableEventType.Current - || args.EventType == LayerTableEventType.Added - ) - { - return; - } - - var layer = RhinoDoc.ActiveDoc.Layers[args.LayerIndex]; - - // add all objects from the changed layers and sublayers to the non-destructively changed object list. - var allLayers = args.Document.Layers.Where(l => l.FullPath.Contains(layer.Name)); // not e imperfect, but layer.GetChildren(true) is valid only in v8 and above; this filter will include the original layer. - foreach (var childLayer in allLayers) - { - var sublayerObjs = RhinoDoc.ActiveDoc.Objects.FindByLayer(childLayer) ?? []; - foreach (var obj in sublayerObjs) - { - ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; - } - } - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + .Subscribe(OnLayerTableEvent); // 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. eventAggregator .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } - - if (args.EventType == MaterialTableEventType.Modified) - { - ChangedMaterialIndexes[args.Index] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }); + .Subscribe(OnMaterialTableEvent); eventAggregator .GetEvent() - .Subscribe(e => - { - if (!_store.IsDocumentInit) - { - return; - } - - // NOTE: not sure yet we want to track every attribute changes yet. TBD - // NOTE: we might want to track here user strings too (once we send them out), and more! - if ( - e.OldAttributes.LayerIndex != e.NewAttributes.LayerIndex - || e.OldAttributes.MaterialSource != e.NewAttributes.MaterialSource - || e.OldAttributes.MaterialIndex != e.NewAttributes.MaterialIndex // NOTE: this does not work when swapping around from custom doc materials, it works when you swap TO/FROM default material - ) - { - ChangedObjectIds[e.RhinoObject.Id.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }); + .Subscribe(OnModifyObjectAttributes); eventAggregator .GetEvent() - .Subscribe(e => + .Subscribe(OnReplaceRhinoObject); + } + + private void OnActiveDocumentChanged(DocumentEventArgs e) => PreviousUnitSystem = e.Document.ModelUnitSystem; + + private async Task OnDocumentPropertiesChanged(DocumentEventArgs e) + { + var newUnit = e.Document.ModelUnitSystem; + if (newUnit != PreviousUnitSystem) + { + PreviousUnitSystem = newUnit; + + await InvalidateAllSender(); + } + } + + private void OnBeginCommandEvent(CommandEventArgs e) + { + if (e.CommandEnglishName == "BlockEdit") + { + var selectedObject = RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false).First(); + ChangedObjectIds[selectedObject.Id.ToString()] = 1; + } + + if (e.CommandEnglishName == "Ungroup") + { + foreach (RhinoObject selectedObject in RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false)) { - if (!_store.IsDocumentInit) - { - return; - } + ChangedObjectIdsInGroupsOrLayers[selectedObject.Id.ToString()] = 1; + } + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } + + private void OnAddRhinoObject(RhinoObjectEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } + + ChangedObjectIds[e.ObjectId.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + + private void OnDeleteRhinoObject(RhinoObjectEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } + + ChangedObjectIds[e.ObjectId.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + + private void OnRenderMaterialsTableEvent(RhinoDoc.RenderContentTableEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } + + if (e is RhinoDoc.RenderMaterialAssignmentChangedEventArgs changedEventArgs) + { + ChangedObjectIds[changedEventArgs.ObjectId.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } + + private void OnGroupTableEvent(GroupTableEventArgs args) + { + if (!_store.IsDocumentInit) + { + return; + } + + foreach (var obj in RhinoDoc.ActiveDoc.Groups.GroupMembers(args.GroupIndex)) + { + ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; + } + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + + private void OnLayerTableEvent(LayerTableEventArgs args) + { + if (!_store.IsDocumentInit) + { + return; + } + + if ( + args.EventType == LayerTableEventType.Deleted + || args.EventType == LayerTableEventType.Current + || args.EventType == LayerTableEventType.Added + ) + { + return; + } + + var layer = RhinoDoc.ActiveDoc.Layers[args.LayerIndex]; + + // add all objects from the changed layers and sublayers to the non-destructively changed object list. + var allLayers = args.Document.Layers.Where(l => l.FullPath.Contains(layer.Name)); // not e imperfect, but layer.GetChildren(true) is valid only in v8 and above; this filter will include the original layer. + foreach (var childLayer in allLayers) + { + var sublayerObjs = RhinoDoc.ActiveDoc.Objects.FindByLayer(childLayer) ?? []; + foreach (var obj in sublayerObjs) + { + ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; + } + } + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + + private void OnMaterialTableEvent(MaterialTableEventArgs args) + { + if (!_store.IsDocumentInit) + { + return; + } + + if (args.EventType == MaterialTableEventType.Modified) + { + ChangedMaterialIndexes[args.Index] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } + + private void OnModifyObjectAttributes(RhinoModifyObjectAttributesEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } + + // NOTE: not sure yet we want to track every attribute changes yet. TBD + // NOTE: we might want to track here user strings too (once we send them out), and more! + if ( + e.OldAttributes.LayerIndex != e.NewAttributes.LayerIndex + || e.OldAttributes.MaterialSource != e.NewAttributes.MaterialSource + || e.OldAttributes.MaterialIndex != e.NewAttributes.MaterialIndex // NOTE: this does not work when swapping around from custom doc materials, it works when you swap TO/FROM default material + ) + { + ChangedObjectIds[e.RhinoObject.Id.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } + + private void OnReplaceRhinoObject(RhinoReplaceObjectEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } - ChangedObjectIds[e.NewRhinoObject.Id.ToString()] = 1; - ChangedObjectIds[e.OldRhinoObject.Id.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + ChangedObjectIds[e.NewRhinoObject.Id.ToString()] = 1; + ChangedObjectIds[e.OldRhinoObject.Id.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); } public List GetSendFilters() => _sendFilters; @@ -342,7 +367,7 @@ public async Task Send(string modelCardId) /// /// Checks if any sender model cards contain any of the changed objects. If so, also updates the changed objects hashset for each model card - this last part is important for on send change detection. /// - private async Task RunExpirationChecks() + private async Task RunExpirationChecks(object _) { // Note: added here a guard against executing this if there's no active doc present. if (RhinoDoc.ActiveDoc == null) diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs index 0e7a68021..3a7fd5dde 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs @@ -1,4 +1,5 @@ using Rhino; +using Rhino.Commands; using Rhino.DocObjects; using Rhino.DocObjects.Tables; using Speckle.Connectors.Common.Threading; @@ -52,6 +53,9 @@ public class GroupTableEvent(IThreadContext threadContext, ITopLevelExceptionHan public class LayerTableEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); +public class BeginCommandEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + public static class RhinoEvents { public static void Register(IEventAggregator eventAggregator) @@ -77,5 +81,7 @@ public static void Register(IEventAggregator eventAggregator) RhinoDoc.ReplaceRhinoObject += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); RhinoDoc.GroupTableEvent += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); RhinoDoc.LayerTableEvent += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); + + Command.BeginCommand += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); } } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs index 00bd73746..9874027c5 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs @@ -8,31 +8,37 @@ namespace Speckle.Connectors.Rhino.HostApp; public class RhinoDocumentStore : DocumentModelStore { + private readonly IEventAggregator _eventAggregator; 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, IEventAggregator eventAggregator) : base(jsonSerializer) { - eventAggregator.GetEvent().Subscribe(_ => IsDocumentInit = false); + _eventAggregator = eventAggregator; + eventAggregator.GetEvent().Subscribe(OnBeginOpenDocument); eventAggregator .GetEvent() - .Subscribe(async e => - { - if (e.Merge) - { - return; - } + .Subscribe(OnEndOpenDocument); + } + + private void OnBeginOpenDocument(object _) => IsDocumentInit = false; + + private async Task OnEndOpenDocument(DocumentOpenEventArgs e) + { + if (e.Merge) + { + return; + } - if (e.Document == null) - { - return; - } + if (e.Document == null) + { + return; + } - IsDocumentInit = true; - LoadState(); - await eventAggregator.GetEvent().PublishAsync(new object()); - }); + IsDocumentInit = true; + LoadState(); + await _eventAggregator.GetEvent().PublishAsync(new object()); } protected override void HostAppSaveState(string modelCardState) diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs index a1e9d2c54..5165a0396 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs @@ -37,12 +37,10 @@ TSM.Model model _model = model; Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - }); + .GetEvent() + .Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs index 5439a2edb..c3c3e3200 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs @@ -23,10 +23,10 @@ IEventAggregator eventAggregator Parent = parent; _selector = selector; - eventAggregator.GetEvent().Subscribe(_ => Events_SelectionChangeEvent()); + eventAggregator.GetEvent().Subscribe(OnSelectionChangeEvent); } - private void Events_SelectionChangeEvent() + private void OnSelectionChangeEvent(object _) { lock (_selectionEventHandlerLock) { diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs index dd7c59dd7..becc5d57b 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs @@ -31,12 +31,14 @@ IEventAggregator eventAggregator GenerateKey(); eventAggregator .GetEvent() - .Subscribe(async _ => - { - GenerateKey(); - LoadState(); - await eventAggregator.GetEvent().PublishAsync(new object()); - }); + .Subscribe(OnModelLoadEvent); + } + + private async Task OnModelLoadEvent(object _) + { + GenerateKey(); + LoadState(); + await _eventAggregator.GetEvent().PublishAsync(new object()); } public override async Task OnDocumentStoreInitialized() @@ -44,7 +46,7 @@ public override async Task OnDocumentStoreInitialized() if (SpeckleTeklaPanelHost.IsInitialized) { LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } } diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index 3ecaefd26..c6c71ac00 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -77,25 +77,24 @@ ITopLevelExceptionHandler topLevelExceptionHandler _topLevelExceptionHandler = topLevelExceptionHandler; eventAggregator .GetEvent() - .Subscribe( - async ex => - { - await Send( - BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, - new - { - type = ToastNotificationType.DANGER, - title = "Unhandled Exception Occurred", - description = ex.ToFormattedString(), - autoClose = false - } - ) - .ConfigureAwait(false); - }, + .Subscribe(OnExceptionEvent, ThreadOption.MainThread ); } + private async Task OnExceptionEvent(Exception ex) => + await Send( + BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, + new + { + type = ToastNotificationType.DANGER, + title = "Unhandled Exception Occurred", + description = ex.ToFormattedString(), + autoClose = false + } + ) + .ConfigureAwait(false); + public void AssociateWithBinding(IBinding binding) { // set via binding property to ensure explosion if already bound diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs index c268769f8..5139e623c 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs @@ -6,7 +6,7 @@ namespace Speckle.Connectors.DUI.Eventing; public class DelegateReference { - private readonly WeakOrStrongReference? _weakReference; + private readonly WeakReference? _weakReference; private readonly MethodInfo _method; private readonly Type? _delegateType; @@ -22,13 +22,11 @@ public DelegateReference(Delegate @delegate, EventFeatures features) || Attribute.IsDefined(_method.DeclaringType.NotNull(), typeof(CompilerGeneratedAttribute)) ) { - _weakReference = WeakOrStrongReference.CreateStrong(target); - } - else - { - _weakReference = WeakOrStrongReference.CreateWeak(target); + throw new EventSubscriptionException("Cannot subscribe to a delegate that was generated by the compiler."); } + _weakReference = new WeakReference(target); + var messageType = @delegate.Method.GetParameters()[0].ParameterType; if (features.HasFlag(EventFeatures.IsAsync)) { @@ -45,20 +43,13 @@ public DelegateReference(Delegate @delegate, EventFeatures features) } } - public bool IsAlive => _weakReference == null || _weakReference.IsAlive; - public async Task Invoke(object message) { - if (!IsAlive) + if (_weakReference?.TryGetTarget(out object target) ?? true) { return false; } - object? target = null; - if (_weakReference != null) - { - target = _weakReference.Target; - } var method = Delegate.CreateDelegate(_delegateType.NotNull(), target, _method); var task = method.DynamicInvoke(message) as Task; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs index a05fde4d5..4ec369801 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs @@ -15,18 +15,12 @@ EventFeatures features { public SubscriptionToken SubscriptionToken => token; - public Func? GetExecutionStrategy() - { - if (!actionReference.IsAlive) - { - return null; - } - return async arguments => + public Func? GetExecutionStrategy() => + async arguments => { TPayload argument = (TPayload)arguments[0]; await InvokeAction(argument); }; - } private async Task InvokeAction(TPayload argument) { diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs index 61354309f..9e5b719c1 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs @@ -6,7 +6,7 @@ namespace Speckle.Connectors.DUI.Eventing; public class ExceptionEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); -public class DocumentChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) +public class DocumentStoreChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); public class IdleEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs index 9414c00dd..f17e3af53 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs @@ -1,6 +1,9 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; +using Speckle.Sdk; +using Speckle.Sdk.Common; namespace Speckle.Connectors.DUI.Eventing; @@ -15,6 +18,7 @@ public abstract class SpeckleEvent(IThreadContext threadContext, ITopLevelExc protected SubscriptionToken Subscribe(Func action, ThreadOption threadOption, EventFeatures features) { + ValidateDelegate(action); features |= EventFeatures.IsAsync; var actionReference = new DelegateReference(action, features); return Subscribe(actionReference, threadOption, features); @@ -26,6 +30,14 @@ protected SubscriptionToken Subscribe(Action action, ThreadOption threadOptio return Subscribe(actionReference, threadOption, features); } + private void ValidateDelegate(Delegate @delegate) + { + if (Attribute.IsDefined(@delegate.Method.DeclaringType.NotNull(), typeof(CompilerGeneratedAttribute))) + { + throw new EventSubscriptionException("Cannot subscribe to a delegate that was generated by the compiler."); + } + } + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] private SubscriptionToken Subscribe( DelegateReference actionReference, @@ -38,3 +50,18 @@ EventFeatures features return InternalSubscribe(subscription); } } + +public class EventSubscriptionException : SpeckleException +{ + public EventSubscriptionException(string message) : base(message) + { + } + + public EventSubscriptionException() + { + } + + public EventSubscriptionException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs deleted file mode 100644 index 98d20ea20..000000000 --- a/DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Speckle.Sdk.Common; - -namespace Speckle.Connectors.DUI.Eventing; - -public class WeakOrStrongReference(WeakReference? weakReference, object? strongReference) -{ - public static WeakOrStrongReference CreateWeak(object? reference) => new(new WeakReference(reference), null); - - public static WeakOrStrongReference CreateStrong(object? reference) => new(null, reference); - - public bool IsAlive => weakReference?.IsAlive ?? true; - - public object Target - { - get - { - if (strongReference is not null) - { - return strongReference; - } - return (weakReference?.Target).NotNull(); - } - } -} From abbde7ada45b820b4b9e70ad6a96733b5ede783f Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 09:40:35 +0000 Subject: [PATCH 04/12] formatting --- .../Bindings/ArcGISSendBinding.cs | 4 +- .../Bindings/BasicConnectorBinding.cs | 7 ++- .../Bindings/AutocadBasicConnectorBinding.cs | 7 ++- .../Bindings/AutocadSendBaseBinding.cs | 1 + .../Bindings/BasicConnectorBindingRevit.cs | 7 ++- .../Bindings/RevitSendBinding.cs | 7 ++- .../Bindings/RhinoBasicConnectorBinding.cs | 5 +- .../Bindings/RhinoSendBinding.cs | 49 +++++-------------- .../Speckle.Connectors.RhinoShared/Events.cs | 2 +- .../HostApp/RhinoDocumentStore.cs | 12 ++--- .../Bindings/TeklaBasicConnectorBinding.cs | 7 ++- .../HostApp/TeklaDocumentModelStore.cs | 4 +- .../Bridge/BrowserBridge.cs | 6 +-- .../Eventing/SpeckleEvent.cs | 22 ++++----- 14 files changed, 48 insertions(+), 92 deletions(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index def6e6188..22fadd614 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -90,9 +90,7 @@ ITopLevelExceptionHandler topLevelExceptionHandler Parent = parent; Commands = new SendBindingUICommands(parent); SubscribeToArcGISEvents(); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache(); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index 57572a42c..b94bb3909 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -34,12 +34,11 @@ IEventAggregator eventAggregator Parent = parent; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } - private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public string GetSourceApplicationName() => _speckleApplication.Slug; public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion; diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs index b9e342c44..053340429 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -41,14 +41,13 @@ IThreadContext threadContext _accountManager = accountManager; _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); _logger = logger; _threadContext = threadContext; } - private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs index f27df5f4f..96ef31285 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs @@ -94,6 +94,7 @@ IEventAggregator eventAggregator } private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache(); + private readonly List _docSubsTracker = new(); private void SubscribeToObjectChanges(Document doc) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index b5cef82c6..781b0a046 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -38,11 +38,10 @@ IEventAggregator eventAggregator _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } - private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); + + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index f45aefaa4..95438ad70 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -83,11 +83,10 @@ ITopLevelExceptionHandler topLevelExceptionHandler revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) => topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e)); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } - private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); + + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); public List GetSendFilters() => [ diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs index 336c87843..3904c2f4b 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs @@ -37,9 +37,7 @@ IEventAggregator eventAggregator _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } private async Task OnDocumentStoreChangedEvent(object _) @@ -49,7 +47,6 @@ private async Task OnDocumentStoreChangedEvent(object _) _sendConversionCache.ClearCache(); } - public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs index f2df9ea38..c27d0b26d 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs @@ -96,55 +96,32 @@ IEventAggregator eventAggregator private void SubscribeToRhinoEvents(IEventAggregator eventAggregator) { - eventAggregator - .GetEvent() - .Subscribe(OnBeginCommandEvent); - - eventAggregator - .GetEvent() - .Subscribe(OnActiveDocumentChanged); + eventAggregator.GetEvent().Subscribe(OnBeginCommandEvent); + + eventAggregator.GetEvent().Subscribe(OnActiveDocumentChanged); // NOTE: BE CAREFUL handling things in this event handler since it is triggered whenever we save something into file! - eventAggregator - .GetEvent() - .Subscribe(OnDocumentPropertiesChanged); + eventAggregator.GetEvent().Subscribe(OnDocumentPropertiesChanged); - eventAggregator - .GetEvent() - .Subscribe(OnAddRhinoObject); + eventAggregator.GetEvent().Subscribe(OnAddRhinoObject); - eventAggregator - .GetEvent() - .Subscribe(OnDeleteRhinoObject); + eventAggregator.GetEvent().Subscribe(OnDeleteRhinoObject); // 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)! - eventAggregator - .GetEvent() - .Subscribe(OnRenderMaterialsTableEvent); + eventAggregator.GetEvent().Subscribe(OnRenderMaterialsTableEvent); - eventAggregator - .GetEvent() - .Subscribe(OnGroupTableEvent - ); + eventAggregator.GetEvent().Subscribe(OnGroupTableEvent); - eventAggregator - .GetEvent() - .Subscribe(OnLayerTableEvent); + eventAggregator.GetEvent().Subscribe(OnLayerTableEvent); // 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. - eventAggregator - .GetEvent() - .Subscribe(OnMaterialTableEvent); + eventAggregator.GetEvent().Subscribe(OnMaterialTableEvent); - eventAggregator - .GetEvent() - .Subscribe(OnModifyObjectAttributes); + eventAggregator.GetEvent().Subscribe(OnModifyObjectAttributes); - eventAggregator - .GetEvent() - .Subscribe(OnReplaceRhinoObject); + eventAggregator.GetEvent().Subscribe(OnReplaceRhinoObject); } - + private void OnActiveDocumentChanged(DocumentEventArgs e) => PreviousUnitSystem = e.Document.ModelUnitSystem; private async Task OnDocumentPropertiesChanged(DocumentEventArgs e) diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs index 3a7fd5dde..67c8acd53 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs @@ -81,7 +81,7 @@ public static void Register(IEventAggregator eventAggregator) RhinoDoc.ReplaceRhinoObject += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); RhinoDoc.GroupTableEvent += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); RhinoDoc.LayerTableEvent += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); - + Command.BeginCommand += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); } } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs index 9874027c5..0048a26a7 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs @@ -17,14 +17,12 @@ public RhinoDocumentStore(IJsonSerializer jsonSerializer, IEventAggregator event { _eventAggregator = eventAggregator; eventAggregator.GetEvent().Subscribe(OnBeginOpenDocument); - eventAggregator - .GetEvent() - .Subscribe(OnEndOpenDocument); + eventAggregator.GetEvent().Subscribe(OnEndOpenDocument); } - - private void OnBeginOpenDocument(object _) => IsDocumentInit = false; - - private async Task OnEndOpenDocument(DocumentOpenEventArgs e) + + private void OnBeginOpenDocument(object _) => IsDocumentInit = false; + + private async Task OnEndOpenDocument(DocumentOpenEventArgs e) { if (e.Merge) { diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs index 5165a0396..c7abd1ae4 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs @@ -36,11 +36,10 @@ TSM.Model model _logger = logger; _model = model; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(OnDocumentStoreChangedEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } - private async Task OnDocumentStoreChangedEvent(object _) =>await Commands.NotifyDocumentChanged(); + + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs index becc5d57b..117722657 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs @@ -29,9 +29,7 @@ IEventAggregator eventAggregator _jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData"); _model = new TSM.Model(); GenerateKey(); - eventAggregator - .GetEvent() - .Subscribe(OnModelLoadEvent); + eventAggregator.GetEvent().Subscribe(OnModelLoadEvent); } private async Task OnModelLoadEvent(object _) diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index c6c71ac00..0939a63d7 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -75,11 +75,7 @@ ITopLevelExceptionHandler topLevelExceptionHandler _browserScriptExecutor = browserScriptExecutor; _threadOptions = threadOptions; _topLevelExceptionHandler = topLevelExceptionHandler; - eventAggregator - .GetEvent() - .Subscribe(OnExceptionEvent, - ThreadOption.MainThread - ); + eventAggregator.GetEvent().Subscribe(OnExceptionEvent, ThreadOption.MainThread); } private async Task OnExceptionEvent(Exception ex) => diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs index f17e3af53..0578641a4 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs @@ -32,10 +32,10 @@ protected SubscriptionToken Subscribe(Action action, ThreadOption threadOptio private void ValidateDelegate(Delegate @delegate) { - if (Attribute.IsDefined(@delegate.Method.DeclaringType.NotNull(), typeof(CompilerGeneratedAttribute))) - { - throw new EventSubscriptionException("Cannot subscribe to a delegate that was generated by the compiler."); - } + if (Attribute.IsDefined(@delegate.Method.DeclaringType.NotNull(), typeof(CompilerGeneratedAttribute))) + { + throw new EventSubscriptionException("Cannot subscribe to a delegate that was generated by the compiler."); + } } [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] @@ -53,15 +53,11 @@ EventFeatures features public class EventSubscriptionException : SpeckleException { - public EventSubscriptionException(string message) : base(message) - { - } + public EventSubscriptionException(string message) + : base(message) { } - public EventSubscriptionException() - { - } + public EventSubscriptionException() { } - public EventSubscriptionException(string message, Exception innerException) : base(message, innerException) - { - } + public EventSubscriptionException(string message, Exception innerException) + : base(message, innerException) { } } From cdb352800931e57f623387498da68032850781c7 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 12:53:01 +0000 Subject: [PATCH 05/12] moved revit changed to fresh branch --- .../Bindings/ArcGISSendBinding.cs | 2 +- .../Bindings/BasicConnectorBinding.cs | 2 +- .../Utils/ArcGisDocumentStore.cs | 4 +- .../Bindings/AutocadBasicConnectorBinding.cs | 2 +- .../Bindings/AutocadSendBaseBinding.cs | 2 +- .../HostApp/AutocadDocumentModelStore.cs | 2 +- .../Bindings/BasicConnectorBindingRevit.cs | 6 +- .../Bindings/RevitBaseBinding.cs | 9 +-- .../Bindings/RevitSendBinding.cs | 52 ++++++++------- .../Bindings/SelectionBinding.cs | 44 ++++--------- .../RevitConnectorModule.cs | 3 +- .../HostApp/ElementUnpacker.cs | 4 +- .../HostApp/RevitDocumentStore.cs | 47 ++++++-------- .../Send/Filters/IRevitSendFilter.cs | 2 +- .../Send/Filters/RevitCategoriesFilter.cs | 8 +-- .../Send/Filters/RevitViewsFilter.cs | 6 +- .../Send/Settings/ToSpeckleSettingsManager.cs | 10 +-- .../Plugin/Events.cs | 63 +++++++++++++++++++ .../Plugin/IRevitPlugin.cs | 4 +- .../Plugin/RevitCefPlugin.cs | 37 +++-------- .../Plugin/RevitExternalApplication.cs | 6 +- .../Speckle.Connectors.RevitShared.projitems | 2 +- .../Bindings/RhinoBasicConnectorBinding.cs | 2 +- .../HostApp/RhinoDocumentStore.cs | 2 +- .../Bindings/TeklaBasicConnectorBinding.cs | 2 +- .../Bindings/TeklaSelectionBinding.cs | 4 +- .../HostApp/TeklaDocumentModelStore.cs | 4 +- .../Helpers/RevitContext.cs | 18 +----- .../ServiceRegistration.cs | 2 - .../RevitConversionSettingsFactory.cs | 5 +- .../Eventing/EventAggregatorTests.cs | 2 +- .../Eventing/DelegateReference.cs | 2 +- .../Speckle.Connectors.DUI/Eventing/Events.cs | 4 +- .../Eventing/OneTimeThreadedEvent.cs | 2 +- .../Eventing/SpeckleEvent.cs | 2 +- .../Eventing/ThreadedEvent.cs | 2 +- 36 files changed, 185 insertions(+), 185 deletions(-) create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index ea887b66f..cd04b4c69 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -91,7 +91,7 @@ ITopLevelExceptionHandler topLevelExceptionHandler Commands = new SendBindingUICommands(parent); SubscribeToArcGISEvents(); eventAggregator - .GetEvent() + .GetEvent() .Subscribe(_ => { _sendConversionCache.ClearCache(); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index 49b51a384..022037c7d 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -35,7 +35,7 @@ IEventAggregator eventAggregator Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() + .GetEvent() .Subscribe(async _ => await Commands.NotifyDocumentChanged().ConfigureAwait(false)); } diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs index e64953be5..1669f0520 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs @@ -51,7 +51,7 @@ public override async Task OnDocumentStoreInitialized() { IsDocumentInit = true; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } } @@ -85,7 +85,7 @@ private async void OnMapViewChanged(ActiveMapViewChangedEventArgs args) IsDocumentInit = true; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(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 8053a6e60..86069bc77 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -42,7 +42,7 @@ IThreadContext threadContext _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() + .GetEvent() .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs index 7c3a7e576..5b20cf93b 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs @@ -90,7 +90,7 @@ IEventAggregator eventAggregator } // Since ids of the objects generates from same seed, we should clear the cache always whenever doc swapped. - eventAggregator.GetEvent().Subscribe(_ => _sendConversionCache.ClearCache()); + eventAggregator.GetEvent().Subscribe(_ => _sendConversionCache.ClearCache()); } private readonly List _docSubsTracker = new(); diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs index 899954633..42e6e61c6 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs @@ -52,7 +52,7 @@ private async void OnDocChangeInternal(Document? doc) _previousDocName = currentDocName; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } protected override void LoadState() diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index 9f8d681e9..394cccb28 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -20,13 +20,13 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding public BasicConnectorBindingCommands Commands { get; } private readonly DocumentModelStore _store; - private readonly RevitContext _revitContext; + private readonly IRevitContext _revitContext; private readonly ISpeckleApplication _speckleApplication; public BasicConnectorBindingRevit( DocumentModelStore store, IBrowserBridge parent, - RevitContext revitContext, + IRevitContext revitContext, ISpeckleApplication speckleApplication, IEventAggregator eventAggregator ) @@ -40,7 +40,7 @@ IEventAggregator eventAggregator // POC: event binding? eventAggregator - .GetEvent() + .GetEvent() .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs index 04c363a18..6f71371f0 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs @@ -1,7 +1,5 @@ using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; -using Speckle.Connectors.DUI.Models; -using Speckle.Converters.RevitShared.Helpers; namespace Speckle.Connectors.Revit.Bindings; @@ -11,14 +9,9 @@ internal abstract class RevitBaseBinding : IBinding public string Name { get; } public IBrowserBridge Parent { get; } - protected readonly DocumentModelStore Store; - protected readonly RevitContext RevitContext; - - protected RevitBaseBinding(string name, DocumentModelStore store, IBrowserBridge parent, RevitContext revitContext) + protected RevitBaseBinding(string name, IBrowserBridge parent) { Name = name; Parent = parent; - Store = store; - RevitContext = revitContext; } } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index 17b4901e3..7adfd7bf9 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -17,6 +17,7 @@ using Speckle.Connectors.DUI.Settings; using Speckle.Connectors.Revit.HostApp; using Speckle.Connectors.Revit.Operations.Send.Settings; +using Speckle.Connectors.Revit.Plugin; using Speckle.Connectors.RevitShared.Operations.Send.Filters; using Speckle.Converters.Common; using Speckle.Converters.RevitShared.Helpers; @@ -28,7 +29,8 @@ namespace Speckle.Connectors.Revit.Bindings; internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding { - private readonly IAppIdleManager _idleManager; + private readonly IRevitContext _revitContext; + private readonly DocumentModelStore _store; private readonly CancellationManager _cancellationManager; private readonly IServiceProvider _serviceProvider; private readonly ISendConversionCache _sendConversionCache; @@ -38,6 +40,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding private readonly ElementUnpacker _elementUnpacker; private readonly IRevitConversionSettingsFactory _revitConversionSettingsFactory; private readonly ISpeckleApplication _speckleApplication; + private readonly IEventAggregator _eventAggregator; /// /// Used internally to aggregate the changed objects' id. Note we're using a concurrent dictionary here as the expiry check method is not thread safe, and this was causing problems. See: @@ -48,8 +51,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding private ConcurrentDictionary ChangedObjectIds { get; set; } = new(); public RevitSendBinding( - IAppIdleManager idleManager, - RevitContext revitContext, + IRevitContext revitContext, DocumentModelStore store, CancellationManager cancellationManager, IBrowserBridge bridge, @@ -61,12 +63,12 @@ public RevitSendBinding( ElementUnpacker elementUnpacker, IRevitConversionSettingsFactory revitConversionSettingsFactory, ISpeckleApplication speckleApplication, - IEventAggregator eventAggregator, - ITopLevelExceptionHandler topLevelExceptionHandler + IEventAggregator eventAggregator ) - : base("sendBinding", store, bridge, revitContext) + : base("sendBinding", bridge) { - _idleManager = idleManager; + _revitContext = revitContext; + _store = store; _cancellationManager = cancellationManager; _serviceProvider = serviceProvider; _sendConversionCache = sendConversionCache; @@ -76,15 +78,15 @@ ITopLevelExceptionHandler topLevelExceptionHandler _elementUnpacker = elementUnpacker; _revitConversionSettingsFactory = revitConversionSettingsFactory; _speckleApplication = speckleApplication; + _eventAggregator = eventAggregator; Commands = new SendBindingUICommands(bridge); // TODO expiry events // TODO filters need refresh events - revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) => - topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e)); + eventAggregator.GetEvent().Subscribe(DocChangeHandler); eventAggregator - .GetEvent() + .GetEvent() .Subscribe(async _ => { await OnDocumentChanged().ConfigureAwait(false); @@ -94,8 +96,8 @@ ITopLevelExceptionHandler topLevelExceptionHandler public List GetSendFilters() => [ new RevitSelectionFilter() { IsDefault = true }, - new RevitViewsFilter(RevitContext), - new RevitCategoriesFilter(RevitContext) + new RevitViewsFilter(_revitContext), + new RevitCategoriesFilter(_revitContext) ]; public List GetSendSettings() => @@ -114,7 +116,7 @@ public async Task Send(string modelCardId) // Note: removed top level handling thing as it was confusing me try { - if (Store.GetModelById(modelCardId) is not SenderModelCard modelCard) + if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard) { // Handle as GLOBAL ERROR at BrowserBridge throw new InvalidOperationException("No publish model card was found."); @@ -174,12 +176,12 @@ public async Task Send(string modelCardId) private async Task> RefreshElementsOnSender(SenderModelCard modelCard) { var activeUIDoc = - RevitContext.UIApplication?.ActiveUIDocument + _revitContext.UIApplication.ActiveUIDocument ?? throw new SpeckleException("Unable to retrieve active UI document"); if (modelCard.SendFilter is IRevitSendFilter viewFilter) { - viewFilter.SetContext(RevitContext); + viewFilter.SetContext(_revitContext); } var selectedObjects = modelCard.SendFilter.NotNull().RefreshObjectIds(); @@ -250,13 +252,13 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs if (addedElementIds.Count > 0) { - _idleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(PostSetObjectIds), _ => PostSetObjectIds()); } if (HaveUnitsChanged(e.GetDocument())) { var objectIds = new List(); - foreach (var sender in Store.GetSenders().ToList()) + foreach (var sender in _store.GetSenders().ToList()) { if (sender.SendFilter is null) { @@ -270,8 +272,10 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs _sendConversionCache.EvictObjects(unpackedObjectIds); } - _idleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration); - _idleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks); + _eventAggregator + .GetEvent() + .OneTimeSubscribe(nameof(CheckFilterExpiration), _ => CheckFilterExpiration()); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RunExpirationChecks), _ => RunExpirationChecks()); } // Keeps track of doc and current units @@ -310,7 +314,7 @@ private bool HaveUnitsChanged(Document doc) private async Task PostSetObjectIds() { - foreach (var sender in Store.GetSenders().ToList()) + foreach (var sender in _store.GetSenders().ToList()) { await RefreshElementsOnSender(sender); } @@ -330,7 +334,7 @@ private async Task CheckFilterExpiration() // await Commands.RefreshSendFilters(); // } - if (ChangedObjectIds.Keys.Any(e => RevitContext.UIApplication?.ActiveUIDocument.Document.GetElement(e) is View)) + if (ChangedObjectIds.Keys.Any(e => _revitContext.UIApplication.ActiveUIDocument.Document.GetElement(e) is View)) { await Commands.RefreshSendFilters(); } @@ -338,9 +342,9 @@ private async Task CheckFilterExpiration() private async Task RunExpirationChecks() { - var senders = Store.GetSenders().ToList(); + var senders = _store.GetSenders().ToList(); // string[] objectIdsList = ChangedObjectIds.Keys.ToArray(); - var doc = RevitContext.UIApplication?.ActiveUIDocument.Document; + var doc = _revitContext.UIApplication.ActiveUIDocument.Document; if (doc == null) { @@ -391,7 +395,7 @@ private async Task RunExpirationChecks() { if (modelCard.SendFilter is IRevitSendFilter viewFilter) { - viewFilter.SetContext(RevitContext); + viewFilter.SetContext(_revitContext); } var selectedObjects = modelCard.SendFilter.NotNull().IdMap.NotNull().Values; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs index a5903928f..246b99835 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs @@ -1,42 +1,27 @@ using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; -using Speckle.Connectors.DUI.Models; +using Speckle.Connectors.DUI.Eventing; +using Speckle.Connectors.Revit.Plugin; using Speckle.Converters.RevitShared.Helpers; using Speckle.Sdk.Common; namespace Speckle.Connectors.Revit.Bindings; // POC: we need a base a RevitBaseBinding -internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding, IDisposable +internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding { -#if REVIT2022 - private readonly System.Timers.Timer _selectionTimer; -#endif + private readonly IRevitContext _revitContext; - public SelectionBinding( - RevitContext revitContext, - DocumentModelStore store, - IAppIdleManager revitIdleManager, - ITopLevelExceptionHandler topLevelExceptionHandler, - IBrowserBridge parent - ) - : base("selectionBinding", store, parent, revitContext) + public SelectionBinding(IRevitContext revitContext, IBrowserBridge parent, IEventAggregator eventAggregator) + : base("selectionBinding", 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 += (_, _) => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged); - _selectionTimer.Start(); -#else - - RevitContext.UIApplication.NotNull().SelectionChanged += (_, _) => - revitIdleManager.SubscribeToIdle(nameof(SelectionBinding), OnSelectionChanged); -#endif + _revitContext = revitContext; + eventAggregator.GetEvent().Subscribe(_ => OnSelectionChanged()); } private void OnSelectionChanged() { - if (RevitContext.UIApplication == null || RevitContext.UIApplication.ActiveUIDocument == null) + if (_revitContext.UIApplication.ActiveUIDocument == null) { return; } @@ -45,12 +30,12 @@ private void OnSelectionChanged() public SelectionInfo GetSelection() { - if (RevitContext.UIApplication == null || RevitContext.UIApplication.ActiveUIDocument == null) + if (_revitContext.UIApplication.ActiveUIDocument == null) { return new SelectionInfo(Array.Empty(), "No objects selected."); } - var activeUIDoc = RevitContext.UIApplication.ActiveUIDocument.NotNull(); + var activeUIDoc = _revitContext.UIApplication.ActiveUIDocument.NotNull(); // POC: this was also being called on shutdown // probably the bridge needs to be able to know if the plugin has been terminated @@ -61,11 +46,4 @@ public SelectionInfo GetSelection() .ToList(); return new SelectionInfo(selectionIds, $"{selectionIds.Count} objects selected."); } - - public void Dispose() - { -#if REVIT2022 - _selectionTimer.Dispose(); -#endif - } } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs index 59dc9da5f..7910a0400 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs @@ -15,6 +15,7 @@ using Speckle.Connectors.Revit.Operations.Send.Settings; using Speckle.Connectors.Revit.Plugin; using Speckle.Converters.Common; +using Speckle.Converters.RevitShared.Helpers; using Speckle.Sdk.Models.GraphTraversal; namespace Speckle.Connectors.Revit.DependencyInjection; @@ -41,10 +42,10 @@ public static void AddRevit(this IServiceCollection serviceCollection) serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(sp => sp.GetRequiredService()); // send operation and dependencies serviceCollection.AddScoped>(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs index b30ec74b0..e2ccb5fa1 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs @@ -9,9 +9,9 @@ namespace Speckle.Connectors.Revit.HostApp; /// public class ElementUnpacker { - private readonly RevitContext _revitContext; + private readonly IRevitContext _revitContext; - public ElementUnpacker(RevitContext revitContext) + public ElementUnpacker(IRevitContext revitContext) { _revitContext = revitContext; } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index f1b8d5e89..5011ff9dc 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -1,13 +1,11 @@ using Autodesk.Revit.DB; using Autodesk.Revit.DB.ExtensibleStorage; -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.Connectors.Revit.Plugin; using Speckle.Converters.RevitShared.Helpers; -using Speckle.Sdk.Common; namespace Speckle.Connectors.Revit.HostApp; @@ -17,38 +15,27 @@ internal sealed class RevitDocumentStore : DocumentModelStore // POC: move to somewhere central? private static readonly Guid s_revitDocumentStoreId = new("D35B3695-EDC9-4E15-B62A-D3FC2CB83FA3"); - private readonly RevitContext _revitContext; - private readonly IAppIdleManager _idleManager; + private readonly IRevitContext _revitContext; private readonly DocumentModelStorageSchema _documentModelStorageSchema; private readonly IdStorageSchema _idStorageSchema; private readonly IEventAggregator _eventAggregator; public RevitDocumentStore( - IAppIdleManager idleManager, - RevitContext revitContext, + IRevitContext revitContext, IJsonSerializer jsonSerializer, DocumentModelStorageSchema documentModelStorageSchema, IdStorageSchema idStorageSchema, - IEventAggregator eventAggregator, - ITopLevelExceptionHandler topLevelExceptionHandler + IEventAggregator eventAggregator ) : base(jsonSerializer) { - _idleManager = idleManager; _revitContext = revitContext; _documentModelStorageSchema = documentModelStorageSchema; _idStorageSchema = idStorageSchema; _eventAggregator = eventAggregator; - UIApplication uiApplication = _revitContext.UIApplication.NotNull(); - - uiApplication.ViewActivated += (s, e) => topLevelExceptionHandler.CatchUnhandled(() => OnViewActivated(s, e)); - - uiApplication.Application.DocumentOpening += (_, _) => - topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false); - - uiApplication.Application.DocumentOpened += (_, _) => - topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false); + eventAggregator.GetEvent().Subscribe(_ => IsDocumentInit = false); + eventAggregator.GetEvent().Subscribe(OnViewActivated); // 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". @@ -56,12 +43,12 @@ ITopLevelExceptionHandler topLevelExceptionHandler } public override Task OnDocumentStoreInitialized() => - _eventAggregator.GetEvent().PublishAsync(new object()); + _eventAggregator.GetEvent().PublishAsync(new object()); /// /// This is the place where we track document switch for new document -> Responsible to Read from new doc /// - private void OnViewActivated(object? _, ViewActivatedEventArgs e) + private void OnViewActivated(ViewActivatedEventArgs e) { if (e.Document == null) { @@ -75,14 +62,16 @@ private void OnViewActivated(object? _, ViewActivatedEventArgs e) } IsDocumentInit = true; - _idleManager.SubscribeToIdle( - nameof(RevitDocumentStore), - async () => - { - LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); - } - ); + _eventAggregator + .GetEvent() + .OneTimeSubscribe( + nameof(RevitDocumentStore), + async _ => + { + LoadState(); + await _eventAggregator.GetEvent().PublishAsync(new object()); + } + ); } protected override void HostAppSaveState(string modelCardState) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/IRevitSendFilter.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/IRevitSendFilter.cs index fe576b8b3..628721059 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/IRevitSendFilter.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/IRevitSendFilter.cs @@ -4,5 +4,5 @@ namespace Speckle.Connectors.RevitShared.Operations.Send.Filters; public interface IRevitSendFilter { - public void SetContext(RevitContext revitContext); + public void SetContext(IRevitContext revitContext); } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitCategoriesFilter.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitCategoriesFilter.cs index 5a3f9b3ee..b926089a6 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitCategoriesFilter.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitCategoriesFilter.cs @@ -11,7 +11,7 @@ public record CategoryData(string Name, string Id); public class RevitCategoriesFilter : DiscriminatedObject, ISendFilter, IRevitSendFilter { - private RevitContext _revitContext; + private IRevitContext _revitContext; private Document? _doc; public string Id { get; set; } = "revitCategories"; public string Name { get; set; } = "Categories"; @@ -24,10 +24,10 @@ public class RevitCategoriesFilter : DiscriminatedObject, ISendFilter, IRevitSen public RevitCategoriesFilter() { } - public RevitCategoriesFilter(RevitContext revitContext) + public RevitCategoriesFilter(IRevitContext revitContext) { _revitContext = revitContext; - _doc = _revitContext.UIApplication?.ActiveUIDocument.Document; + _doc = _revitContext.UIApplication.ActiveUIDocument.Document; GetCategories(); } @@ -83,7 +83,7 @@ private void GetCategories() /// NOTE: this is needed since we need doc on `GetObjectIds()` function after it deserialized. /// DI doesn't help here to pass RevitContext from constructor. /// - public void SetContext(RevitContext revitContext) + public void SetContext(IRevitContext revitContext) { _revitContext = revitContext; _doc = _revitContext.UIApplication?.ActiveUIDocument.Document; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs index ac54ab0c6..41af5b1b9 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs @@ -8,7 +8,7 @@ namespace Speckle.Connectors.RevitShared.Operations.Send.Filters; public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilter { - private RevitContext _revitContext; + private IRevitContext _revitContext; private Document? _doc; public string Id { get; set; } = "revitViews"; public string Name { get; set; } = "Views"; @@ -21,7 +21,7 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt public RevitViewsFilter() { } - public RevitViewsFilter(RevitContext revitContext) + public RevitViewsFilter(IRevitContext revitContext) { _revitContext = revitContext; _doc = _revitContext.UIApplication?.ActiveUIDocument.Document; @@ -109,7 +109,7 @@ or ViewType.AreaPlan /// NOTE: this is needed since we need doc on `GetObjectIds()` function after it deserialized. /// DI doesn't help here to pass RevitContext from constructor. /// - public void SetContext(RevitContext revitContext) + public void SetContext(IRevitContext revitContext) { _revitContext = revitContext; _doc = _revitContext.UIApplication?.ActiveUIDocument.Document; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs index f1e55df9b..5fd21feed 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs @@ -13,7 +13,7 @@ namespace Speckle.Connectors.Revit.Operations.Send.Settings; [GenerateAutoInterface] public class ToSpeckleSettingsManager : IToSpeckleSettingsManager { - private readonly RevitContext _revitContext; + private readonly IRevitContext _revitContext; private readonly ISendConversionCache _sendConversionCache; private readonly ElementUnpacker _elementUnpacker; @@ -23,7 +23,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager private readonly Dictionary _sendNullParamsCache = new(); public ToSpeckleSettingsManager( - RevitContext revitContext, + IRevitContext revitContext, ISendConversionCache sendConversionCache, ElementUnpacker elementUnpacker ) @@ -68,7 +68,7 @@ out ReferencePointType referencePoint { // get the current transform from setting first // we are doing this because we can't track if reference points were changed between send operations. - Transform? currentTransform = GetTransform(_revitContext, referencePoint); + Transform? currentTransform = GetTransform(referencePoint); if (_referencePointCache.TryGetValue(modelCard.ModelCardId.NotNull(), out Transform? previousTransform)) { @@ -109,11 +109,11 @@ private void EvictCacheForModelCard(SenderModelCard modelCard) _sendConversionCache.EvictObjects(unpackedObjectIds); } - private Transform? GetTransform(RevitContext context, ReferencePointType referencePointType) + private Transform? GetTransform(ReferencePointType referencePointType) { Transform? referencePointTransform = null; - if (context.UIApplication is UIApplication uiApplication) + if (_revitContext.UIApplication is UIApplication uiApplication) { // first get the main doc base points and reference setting transform using FilteredElementCollector filteredElementCollector = new(uiApplication.ActiveUIDocument.Document); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs new file mode 100644 index 000000000..84ee01666 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs @@ -0,0 +1,63 @@ +using Autodesk.Revit.ApplicationServices; +using Autodesk.Revit.UI; +using Autodesk.Revit.UI.Events; +using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Eventing; + +namespace Speckle.Connectors.Revit.Plugin; + +public class ApplicationInitializedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class ViewActivatedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DocumentStoreInitializingEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class SelectionChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public class DocumentChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + +public static class RevitEvents +{ +#if REVIT2022 + private static readonly System.Timers.Timer s_selectionTimer = new(1000); +#endif + + public static void Register(IEventAggregator eventAggregator, UIControlledApplication application) + { + application.Idling += async (_, _) => await eventAggregator.GetEvent().PublishAsync(new object()); + application.ControlledApplication.ApplicationInitialized += async (sender, _) => + await eventAggregator + .GetEvent() + .PublishAsync(new UIApplication(sender as Application)); + application.ViewActivated += async (_, args) => + await eventAggregator.GetEvent().PublishAsync(args); + application.ControlledApplication.DocumentOpened += async (_, _) => + await eventAggregator.GetEvent().PublishAsync(new object()); + application.ControlledApplication.DocumentOpening += async (_, _) => + await eventAggregator.GetEvent().PublishAsync(new object()); + application.ControlledApplication.DocumentChanged += async (_, args) => + await eventAggregator.GetEvent().PublishAsync(args); + +#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. + s_selectionTimer.Elapsed += async (_, _) => + await eventAggregator.GetEvent().PublishAsync(new object()); + s_selectionTimer.Start(); +#else + + application.SelectionChanged += (_, _) => + eventAggregator + .GetEvent() + .OneTimeSubscribe( + "Selection", + _ => eventAggregator.GetEvent().PublishAsync(new object()) + ); +#endif + } +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/IRevitPlugin.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/IRevitPlugin.cs index fb88ffc44..5ba7f1d52 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/IRevitPlugin.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/IRevitPlugin.cs @@ -1,6 +1,8 @@ +using Speckle.Converters.RevitShared.Helpers; + namespace Speckle.Connectors.Revit.Plugin; -internal interface IRevitPlugin +internal interface IRevitPlugin : IRevitContext { void Initialise(); void Shutdown(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs index fb23025c7..1cbc084ee 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs @@ -3,16 +3,13 @@ using System.Reflection; using System.Windows.Media; using System.Windows.Media.Imaging; -using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.UI; using CefSharp; using Microsoft.Extensions.DependencyInjection; -using Revit.Async; using Speckle.Connectors.Common; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; -using Speckle.Connectors.DUI.Models; -using Speckle.Converters.RevitShared.Helpers; +using Speckle.Connectors.DUI.Eventing; using Speckle.Sdk; namespace Speckle.Connectors.Revit.Plugin; @@ -22,7 +19,6 @@ internal sealed class RevitCefPlugin : IRevitPlugin private readonly UIControlledApplication _uIControlledApplication; private readonly IServiceProvider _serviceProvider; // should be lazy to ensure the bindings are not created too early private readonly BindingOptions _bindingOptions; - private readonly RevitContext _revitContext; private readonly CefSharpPanel _cefSharpPanel; private readonly ISpeckleApplication _speckleApplication; @@ -30,17 +26,17 @@ public RevitCefPlugin( UIControlledApplication uIControlledApplication, IServiceProvider serviceProvider, BindingOptions bindingOptions, - RevitContext revitContext, CefSharpPanel cefSharpPanel, - ISpeckleApplication speckleApplication + ISpeckleApplication speckleApplication, + IEventAggregator eventAggregator ) { _uIControlledApplication = uIControlledApplication; _serviceProvider = serviceProvider; _bindingOptions = bindingOptions; - _revitContext = revitContext; _cefSharpPanel = cefSharpPanel; _speckleApplication = speckleApplication; + eventAggregator.GetEvent().Subscribe(OnApplicationInitialized); } public void Initialise() @@ -48,7 +44,6 @@ public void Initialise() // Create and register panels before app initialized. this is needed for double-click file open CreateTabAndRibbonPanel(_uIControlledApplication); RegisterDockablePane(); - _uIControlledApplication.ControlledApplication.ApplicationInitialized += OnApplicationInitialized; } public void Shutdown() @@ -100,26 +95,10 @@ private void CreateTabAndRibbonPanel(UIControlledApplication application) dui3Button.SetContextualHelp(new ContextualHelp(ContextualHelpType.Url, "https://speckle.systems")); } - private async void OnApplicationInitialized( - object? sender, - Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e - ) + private void OnApplicationInitialized(UIApplication uiApplication) { - var uiApplication = new UIApplication(sender as Application); - _revitContext.UIApplication = uiApplication; - - // POC: might be worth to interface this out, we shall see... - RevitTask.Initialize(uiApplication); - await _serviceProvider.GetRequiredService().OnDocumentStoreInitialized(); - - PostApplicationInit(); // for double-click file open - } + UIApplication = uiApplication; - /// - /// Actions to run after UiApplication initialized. This is needed for double-click file open issue. - /// - private void PostApplicationInit() - { var bindings = _serviceProvider.GetRequiredService>(); // binding the bindings to each bridge foreach (IBinding binding in bindings) @@ -128,7 +107,7 @@ private void PostApplicationInit() binding.Parent.AssociateWithBinding(binding); } - _cefSharpPanel.Browser.IsBrowserInitializedChanged += (sender, e) => + _cefSharpPanel.Browser.IsBrowserInitializedChanged += (_, e) => { if (e.NewValue is false) { @@ -186,4 +165,6 @@ private void RegisterDockablePane() return null; } + + public UIApplication UIApplication { get; private set; } } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs index 05ee7ddb8..35ea4541b 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs @@ -1,8 +1,10 @@ using Autodesk.Revit.UI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Revit.Async; using Speckle.Connectors.Common; using Speckle.Connectors.DUI; +using Speckle.Connectors.DUI.Eventing; using Speckle.Connectors.Revit.DependencyInjection; using Speckle.Converters.RevitShared; using Speckle.Sdk; @@ -48,8 +50,10 @@ public Result OnStartup(UIControlledApplication application) services.AddRevitConverters(); services.AddSingleton(application); _container = services.BuildServiceProvider(); - _container.UseDUI(true); + _container.UseDUI(); + RevitTask.Initialize(application); + RevitEvents.Register(_container.GetRequiredService(), application); // resolve root object _revitPlugin = _container.GetRequiredService(); _revitPlugin.Initialise(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems index 890736c53..485644d2e 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems @@ -44,10 +44,10 @@ + - diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs index 1cdc94ed1..bf7aab767 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs @@ -38,7 +38,7 @@ IEventAggregator eventAggregator Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() + .GetEvent() .Subscribe(async _ => { await Commands.NotifyDocumentChanged(); diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs index 00bd73746..5575d6e0a 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs @@ -31,7 +31,7 @@ public RhinoDocumentStore(IJsonSerializer jsonSerializer, IEventAggregator event IsDocumentInit = true; LoadState(); - await eventAggregator.GetEvent().PublishAsync(new object()); + await eventAggregator.GetEvent().PublishAsync(new object()); }); } diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs index a1e9d2c54..c00389427 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs @@ -37,7 +37,7 @@ TSM.Model model _model = model; Commands = new BasicConnectorBindingCommands(parent); eventAggregator - .GetEvent() + .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 5439a2edb..159e991dc 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs @@ -8,8 +8,9 @@ namespace Speckle.Connectors.TeklaShared.Bindings; public class TeklaSelectionBinding : ISelectionBinding { private const string SELECTION_EVENT = "setSelection"; - private readonly object _selectionEventHandlerLock = new(); + 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; } @@ -22,6 +23,7 @@ IEventAggregator eventAggregator { Parent = parent; _selector = selector; + _eventAggregator = eventAggregator; eventAggregator.GetEvent().Subscribe(_ => Events_SelectionChangeEvent()); } diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs index dd7c59dd7..f18a0dae5 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs @@ -35,7 +35,7 @@ IEventAggregator eventAggregator { GenerateKey(); LoadState(); - await eventAggregator.GetEvent().PublishAsync(new object()); + await eventAggregator.GetEvent().PublishAsync(new object()); }); } @@ -44,7 +44,7 @@ public override async Task OnDocumentStoreInitialized() if (SpeckleTeklaPanelHost.IsInitialized) { LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } } diff --git a/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitContext.cs b/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitContext.cs index 59c3892a7..7c9702600 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitContext.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitContext.cs @@ -2,21 +2,7 @@ namespace Speckle.Converters.RevitShared.Helpers; -public class RevitContext +public interface IRevitContext { - private UIApplication? _uiApplication; - - public UIApplication? UIApplication - { - get => _uiApplication; - set - { - if (_uiApplication != null) - { - throw new ArgumentException("UIApplication already set"); - } - - _uiApplication = value; - } - } + public UIApplication UIApplication { get; } } diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ServiceRegistration.cs b/Converters/Revit/Speckle.Converters.RevitShared/ServiceRegistration.cs index b6b8e1bef..1b42b7156 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/ServiceRegistration.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/ServiceRegistration.cs @@ -25,8 +25,6 @@ public static IServiceCollection AddRevitConverters(this IServiceCollection serv serviceCollection.AddApplicationConverters(converterAssembly); serviceCollection.AddScoped(); - serviceCollection.AddSingleton(new RevitContext()); - serviceCollection.AddSingleton(new RevitToHostCacheSingleton()); serviceCollection.AddSingleton(new RevitToSpeckleCacheSingleton()); diff --git a/Converters/Revit/Speckle.Converters.RevitShared/Settings/RevitConversionSettingsFactory.cs b/Converters/Revit/Speckle.Converters.RevitShared/Settings/RevitConversionSettingsFactory.cs index 93339a409..f1ea559f6 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/Settings/RevitConversionSettingsFactory.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/Settings/RevitConversionSettingsFactory.cs @@ -1,13 +1,12 @@ using Speckle.Converters.Common; using Speckle.Converters.RevitShared.Helpers; using Speckle.InterfaceGenerator; -using Speckle.Sdk.Common; namespace Speckle.Converters.RevitShared.Settings; [GenerateAutoInterface] public class RevitConversionSettingsFactory( - RevitContext revitContext, + IRevitContext revitContext, IHostToSpeckleUnitConverter unitConverter ) : IRevitConversionSettingsFactory { @@ -18,7 +17,7 @@ public RevitConversionSettings Create( double tolerance = 0.0164042 // 5mm in ft ) { - var document = revitContext.UIApplication.NotNull().ActiveUIDocument.Document; + var document = revitContext.UIApplication.ActiveUIDocument.Document; return new( document, detailLevelType, diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs index 0441bb05d..e91ffc84b 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NUnit.Framework; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs index c268769f8..05145c540 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using Speckle.Sdk.Common; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs index 61354309f..05c3cc59d 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs @@ -1,4 +1,4 @@ -using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; namespace Speckle.Connectors.DUI.Eventing; @@ -6,7 +6,7 @@ namespace Speckle.Connectors.DUI.Eventing; public class ExceptionEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); -public class DocumentChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) +public class DocumentStoreChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); public class IdleEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs index 75e41531b..4e5e0de23 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs @@ -1,4 +1,4 @@ -using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; namespace Speckle.Connectors.DUI.Eventing; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs index 9414c00dd..e44a73bd7 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs index 95f714191..51ae9d6fb 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/ThreadedEvent.cs @@ -1,4 +1,4 @@ -using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; namespace Speckle.Connectors.DUI.Eventing; From 834bd7278864a95e527521688632484b907e2cfb Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 14:06:20 +0000 Subject: [PATCH 06/12] fix tests --- .../Eventing/EventAggregatorTests.cs | 57 +++++++------------ .../Eventing/DelegateReference.cs | 2 +- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs index 0441bb05d..7d20e3ea8 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -47,16 +47,10 @@ public async Task Sub_Async_DisposeToken() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Sub_Async_DisposeToken(IServiceProvider serviceProvider) + private SubscriptionToken Test_Sub_Async_DisposeToken(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .Subscribe(_ => - { - s_val = true; - return Task.CompletedTask; - }); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestAsyncSubscribe); return subscriptionToken; } @@ -90,15 +84,10 @@ public async Task Sub_Async_SubscribeToken() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) + private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .Subscribe(_ => - { - s_val = true; - }); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestSyncSubscribe); return subscriptionToken; } @@ -132,22 +121,19 @@ public async Task Sub_Sync() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) + private SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .OneTimeSubscribe( - "test", - _ => - { - s_val = true; - return Task.CompletedTask; - } - ); + var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestAsyncSubscribe); return subscriptionToken; } + private Task OnTestAsyncSubscribe(object _) + { + s_val = true; + return Task.CompletedTask; + } + [Test] public async Task Onetime_Async() { @@ -183,21 +169,18 @@ public async Task Onetime_Async() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Onetime_Sub_Sync(IServiceProvider serviceProvider) + private SubscriptionToken Test_Onetime_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .OneTimeSubscribe( - "test", - _ => - { - s_val = true; - } - ); + var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestSyncSubscribe); return subscriptionToken; } + private void OnTestSyncSubscribe(object _) + { + s_val = true; + } + [Test] public async Task Onetime_Sync() { diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs index 5139e623c..b3a63831d 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs @@ -45,7 +45,7 @@ public DelegateReference(Delegate @delegate, EventFeatures features) public async Task Invoke(object message) { - if (_weakReference?.TryGetTarget(out object target) ?? true) + if (_weakReference == null || !_weakReference.TryGetTarget(out object target)) { return false; } From 84ea5c2f44d9548e58dd23c62dbdf79d287682e4 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 14:17:10 +0000 Subject: [PATCH 07/12] weakreference found should remove subscription --- .../Eventing/EventSubscription.cs | 18 +++++++++++------- .../Eventing/EventSubscriptionException.cs | 14 ++++++++++++++ .../Eventing/SpeckleEvent.cs | 12 ------------ 3 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs index 4ec369801..4f38e3741 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs @@ -39,12 +39,16 @@ private async Task InvokeAction(TPayload argument) } } - private async Task Invoke(TPayload argument) - { - await exceptionHandler.CatchUnhandledAsync(() => actionReference.Invoke(argument)); - if (features.HasFlag(EventFeatures.OneTime)) + private async Task Invoke(TPayload argument) => + await exceptionHandler.CatchUnhandledAsync(async () => { - SubscriptionToken.Dispose(); - } - } + if (!(await actionReference.Invoke(argument))) + { + SubscriptionToken.Dispose(); + } + else if (features.HasFlag(EventFeatures.OneTime)) + { + SubscriptionToken.Dispose(); + } + }); } diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs new file mode 100644 index 000000000..e0fe936d6 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs @@ -0,0 +1,14 @@ +using Speckle.Sdk; + +namespace Speckle.Connectors.DUI.Eventing; + +public class EventSubscriptionException : SpeckleException +{ + public EventSubscriptionException(string message) + : base(message) { } + + public EventSubscriptionException() { } + + public EventSubscriptionException(string message, Exception innerException) + : base(message, innerException) { } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs index 0578641a4..c0d2ae85a 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; -using Speckle.Sdk; using Speckle.Sdk.Common; namespace Speckle.Connectors.DUI.Eventing; @@ -50,14 +49,3 @@ EventFeatures features return InternalSubscribe(subscription); } } - -public class EventSubscriptionException : SpeckleException -{ - public EventSubscriptionException(string message) - : base(message) { } - - public EventSubscriptionException() { } - - public EventSubscriptionException(string message, Exception innerException) - : base(message, innerException) { } -} From cd5dd2818d2b84c3fa7bff10930d8685e75bbb5f Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 15:17:25 +0000 Subject: [PATCH 08/12] Fix anonymous lambdas --- .../Bindings/RevitSendBinding.cs | 12 ++++++------ .../Bindings/SelectionBinding.cs | 4 ++-- .../HostApp/RevitDocumentStore.cs | 18 ++++++++++++------ .../Plugin/Events.cs | 13 ++++++++++++- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index bf6df2e34..07e738bf5 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -249,7 +249,7 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs if (addedElementIds.Count > 0) { - _eventAggregator.GetEvent().OneTimeSubscribe(nameof(PostSetObjectIds), _ => PostSetObjectIds()); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(PostSetObjectIds), PostSetObjectIds); } if (HaveUnitsChanged(e.GetDocument())) @@ -271,8 +271,8 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs _eventAggregator .GetEvent() - .OneTimeSubscribe(nameof(CheckFilterExpiration), _ => CheckFilterExpiration()); - _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RunExpirationChecks), _ => RunExpirationChecks()); + .OneTimeSubscribe(nameof(CheckFilterExpiration), CheckFilterExpiration); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RunExpirationChecks), RunExpirationChecks); } // Keeps track of doc and current units @@ -309,7 +309,7 @@ private bool HaveUnitsChanged(Document doc) return false; } - private async Task PostSetObjectIds() + private async Task PostSetObjectIds(object _) { foreach (var sender in _store.GetSenders().ToList()) { @@ -320,7 +320,7 @@ private async Task PostSetObjectIds() /// /// Notifies ui if any filters need refreshing. Currently, this only applies for view filters. /// - private async Task CheckFilterExpiration() + private async Task CheckFilterExpiration(object _) { // NOTE: below code seems like more make sense in terms of performance but it causes unmanaged exception on Revit // using var viewCollector = new FilteredElementCollector(RevitContext.UIApplication?.ActiveUIDocument.Document); @@ -337,7 +337,7 @@ private async Task CheckFilterExpiration() } } - private async Task RunExpirationChecks() + private async Task RunExpirationChecks(object _) { var senders = _store.GetSenders().ToList(); // string[] objectIdsList = ChangedObjectIds.Keys.ToArray(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs index 246b99835..6c194ce67 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs @@ -16,10 +16,10 @@ public SelectionBinding(IRevitContext revitContext, IBrowserBridge parent, IEven : base("selectionBinding", parent) { _revitContext = revitContext; - eventAggregator.GetEvent().Subscribe(_ => OnSelectionChanged()); + eventAggregator.GetEvent().Subscribe(OnSelectionChanged); } - private void OnSelectionChanged() + private void OnSelectionChanged(object _) { if (_revitContext.UIApplication.ActiveUIDocument == null) { diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index 5011ff9dc..7a151d5c1 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -34,13 +34,17 @@ IEventAggregator eventAggregator _idStorageSchema = idStorageSchema; _eventAggregator = eventAggregator; - eventAggregator.GetEvent().Subscribe(_ => IsDocumentInit = false); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreInitializingEvent); eventAggregator.GetEvent().Subscribe(OnViewActivated); // 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(); } + + + public void OnDocumentStoreInitializingEvent(object _) => + IsDocumentInit = false; public override Task OnDocumentStoreInitialized() => _eventAggregator.GetEvent().PublishAsync(new object()); @@ -66,14 +70,16 @@ private void OnViewActivated(ViewActivatedEventArgs e) .GetEvent() .OneTimeSubscribe( nameof(RevitDocumentStore), - async _ => - { - LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); - } + OnIdleEvent ); } + private async Task OnIdleEvent(object _) + { + LoadState(); + await _eventAggregator.GetEvent().PublishAsync(new object()); + } + protected override void HostAppSaveState(string modelCardState) { var doc = _revitContext.UIApplication?.ActiveUIDocument?.Document; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs index 84ee01666..767865336 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs @@ -28,8 +28,10 @@ public static class RevitEvents private static readonly System.Timers.Timer s_selectionTimer = new(1000); #endif + private static IEventAggregator? s_eventAggregator; public static void Register(IEventAggregator eventAggregator, UIControlledApplication application) { + s_eventAggregator = eventAggregator; application.Idling += async (_, _) => await eventAggregator.GetEvent().PublishAsync(new object()); application.ControlledApplication.ApplicationInitialized += async (sender, _) => await eventAggregator @@ -56,8 +58,17 @@ await eventAggregator .GetEvent() .OneTimeSubscribe( "Selection", - _ => eventAggregator.GetEvent().PublishAsync(new object()) + OnSelectionChanged ); #endif } + + private static async Task OnSelectionChanged(object _) + { + if (s_eventAggregator is null) + { + return; + } + await s_eventAggregator.GetEvent().PublishAsync(new object()); + } } From f7e47ec42fc13e559ce574ed34c8a17a1f5aef1a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 22 Jan 2025 15:26:18 +0000 Subject: [PATCH 09/12] allow for static handlers and add tests --- .../Eventing/EventAggregatorTests.cs | 91 ++++++++++++++++++- .../Eventing/DelegateReference.cs | 44 +++++---- 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs index 7d20e3ea8..6efd5cea6 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs @@ -84,6 +84,36 @@ public async Task Sub_Async_SubscribeToken() subscriptionToken.IsActive.Should().BeFalse(); } + [Test] + public async Task Sub_Sync() + { + s_val = false; + var services = new ServiceCollection(); + var exceptionHandler = new TopLevelExceptionHandler( + Create>().Object, + Create().Object + ); + services.AddSingleton(Create().Object); + services.AddSingleton(exceptionHandler); + services.AddTransient(); + + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + var subscriptionToken = Test_Sub_Sync(serviceProvider); + var eventAggregator = serviceProvider.GetRequiredService(); + await eventAggregator.GetEvent().PublishAsync(new object()); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + subscriptionToken.IsActive.Should().BeTrue(); + s_val.Should().BeTrue(); + eventAggregator.GetEvent().Unsubscribe(subscriptionToken); + GC.Collect(); + GC.WaitForPendingFinalizers(); + subscriptionToken.IsActive.Should().BeFalse(); + } + private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); @@ -91,8 +121,9 @@ private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) return subscriptionToken; } + [Test] - public async Task Sub_Sync() + public async Task Sub_Sync_Static() { s_val = false; var services = new ServiceCollection(); @@ -107,7 +138,7 @@ public async Task Sub_Sync() services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); - var subscriptionToken = Test_Sub_Sync(serviceProvider); + var subscriptionToken = Test_Sub_Sync_Static(serviceProvider); var eventAggregator = serviceProvider.GetRequiredService(); await eventAggregator.GetEvent().PublishAsync(new object()); @@ -120,13 +151,58 @@ public async Task Sub_Sync() GC.WaitForPendingFinalizers(); subscriptionToken.IsActive.Should().BeFalse(); } + private static SubscriptionToken Test_Sub_Sync_Static(IServiceProvider serviceProvider) + { + var eventAggregator = serviceProvider.GetRequiredService(); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestSyncStaticSubscribe); + return subscriptionToken; + } + private static void OnTestSyncStaticSubscribe(object _) + { + s_val = true; + } - private SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) + + [Test] + public async Task Sub_Async_Static() { + s_val = false; + var services = new ServiceCollection(); + var exceptionHandler = new TopLevelExceptionHandler( + Create>().Object, + Create().Object + ); + services.AddSingleton(Create().Object); + services.AddSingleton(exceptionHandler); + services.AddTransient(); + + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + var subscriptionToken = Test_Sub_Async_Static(serviceProvider); var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestAsyncSubscribe); + await eventAggregator.GetEvent().PublishAsync(new object()); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + subscriptionToken.IsActive.Should().BeTrue(); + s_val.Should().BeTrue(); + eventAggregator.GetEvent().Unsubscribe(subscriptionToken); + GC.Collect(); + GC.WaitForPendingFinalizers(); + subscriptionToken.IsActive.Should().BeFalse(); + } + private static SubscriptionToken Test_Sub_Async_Static(IServiceProvider serviceProvider) + { + var eventAggregator = serviceProvider.GetRequiredService(); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestAsyncStaticSubscribe); return subscriptionToken; } + private static Task OnTestAsyncStaticSubscribe(object _) + { + s_val = true; + return Task.CompletedTask; + } private Task OnTestAsyncSubscribe(object _) { @@ -169,6 +245,13 @@ public async Task Onetime_Async() subscriptionToken.IsActive.Should().BeFalse(); } + private SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) + { + var eventAggregator = serviceProvider.GetRequiredService(); + var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestAsyncSubscribe); + return subscriptionToken; + } + private SubscriptionToken Test_Onetime_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs index 3160e3dc2..153b224e8 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs @@ -8,12 +8,21 @@ public class DelegateReference { private readonly WeakReference? _weakReference; private readonly MethodInfo _method; - private readonly Type? _delegateType; + private readonly Type _delegateType; public DelegateReference(Delegate @delegate, EventFeatures features) { var target = @delegate.Target; _method = @delegate.Method; + var messageType = @delegate.Method.GetParameters()[0].ParameterType; + if (features.HasFlag(EventFeatures.IsAsync)) + { + _delegateType = typeof(Func<,>).MakeGenericType(messageType, typeof(Task)); + } + else + { + _delegateType = typeof(Action<>).MakeGenericType(messageType); + } if (target != null) { //anonymous methods are always strong....should we do this? - doing a brief search says yes @@ -26,16 +35,6 @@ public DelegateReference(Delegate @delegate, EventFeatures features) } _weakReference = new WeakReference(target); - - var messageType = @delegate.Method.GetParameters()[0].ParameterType; - if (features.HasFlag(EventFeatures.IsAsync)) - { - _delegateType = typeof(Func<,>).MakeGenericType(messageType, typeof(Task)); - } - else - { - _delegateType = typeof(Action<>).MakeGenericType(messageType); - } } else { @@ -45,18 +44,31 @@ public DelegateReference(Delegate @delegate, EventFeatures features) public async Task Invoke(object message) { - if (_weakReference == null || !_weakReference.TryGetTarget(out object target)) + if (_weakReference == null) + { + var method = Delegate.CreateDelegate(_delegateType, null, _method); + + var task = method.DynamicInvoke(message) as Task; + + if (task is not null) + { + await task; + } + + return true; + } + if (!_weakReference.TryGetTarget(out object target)) { return false; } - var method = Delegate.CreateDelegate(_delegateType.NotNull(), target, _method); + var method2 = Delegate.CreateDelegate(_delegateType, target, _method); - var task = method.DynamicInvoke(message) as Task; + var task2 = method2.DynamicInvoke(message) as Task; - if (task is not null) + if (task2 is not null) { - await task; + await task2; } return true; From 75eefb2ae8d74020d85f008f4a976c2cc52fe44b Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 23 Jan 2025 11:13:08 +0000 Subject: [PATCH 10/12] Clean up --- .../HostApp/RevitDocumentStore.cs | 5 +++-- .../Plugin/{Events.cs => RevitEvents.cs} | 15 +++++++++++---- .../Plugin/RevitExternalApplication.cs | 2 +- .../Speckle.Connectors.RevitShared.projitems | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) rename Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/{Events.cs => RevitEvents.cs} (85%) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index 7a151d5c1..8c96692e7 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -34,7 +34,8 @@ IEventAggregator eventAggregator _idStorageSchema = idStorageSchema; _eventAggregator = eventAggregator; - eventAggregator.GetEvent().Subscribe(OnDocumentStoreInitializingEvent); + eventAggregator.GetEvent().Subscribe(OnDocumentOpen); + eventAggregator.GetEvent().Subscribe(OnDocumentOpen); eventAggregator.GetEvent().Subscribe(OnViewActivated); // There is no event that we can hook here for double-click file open... @@ -43,7 +44,7 @@ IEventAggregator eventAggregator } - public void OnDocumentStoreInitializingEvent(object _) => + private void OnDocumentOpen(object _) => IsDocumentInit = false; public override Task OnDocumentStoreInitialized() => diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs similarity index 85% rename from Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs rename to Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs index 767865336..374d0abae 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/Events.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs @@ -13,7 +13,9 @@ public class ApplicationInitializedEvent(IThreadContext threadContext, ITopLevel public class ViewActivatedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); -public class DocumentStoreInitializingEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) +public class DocumentOpeningEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); +public class DocumentOpenedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); public class SelectionChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) @@ -26,12 +28,15 @@ public static class RevitEvents { #if REVIT2022 private static readonly System.Timers.Timer s_selectionTimer = new(1000); +#else + private static IEventAggregator? s_eventAggregator; #endif - private static IEventAggregator? s_eventAggregator; public static void Register(IEventAggregator eventAggregator, UIControlledApplication application) { +#if !REVIT2022 s_eventAggregator = eventAggregator; +#endif application.Idling += async (_, _) => await eventAggregator.GetEvent().PublishAsync(new object()); application.ControlledApplication.ApplicationInitialized += async (sender, _) => await eventAggregator @@ -40,9 +45,9 @@ await eventAggregator application.ViewActivated += async (_, args) => await eventAggregator.GetEvent().PublishAsync(args); application.ControlledApplication.DocumentOpened += async (_, _) => - await eventAggregator.GetEvent().PublishAsync(new object()); + await eventAggregator.GetEvent().PublishAsync(new object()); application.ControlledApplication.DocumentOpening += async (_, _) => - await eventAggregator.GetEvent().PublishAsync(new object()); + await eventAggregator.GetEvent().PublishAsync(new object()); application.ControlledApplication.DocumentChanged += async (_, args) => await eventAggregator.GetEvent().PublishAsync(args); @@ -63,6 +68,7 @@ await eventAggregator #endif } +#if !REVIT2022 private static async Task OnSelectionChanged(object _) { if (s_eventAggregator is null) @@ -71,4 +77,5 @@ private static async Task OnSelectionChanged(object _) } await s_eventAggregator.GetEvent().PublishAsync(new object()); } +#endif } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs index 9e63a26cc..35ea4541b 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs @@ -50,7 +50,7 @@ public Result OnStartup(UIControlledApplication application) services.AddRevitConverters(); services.AddSingleton(application); _container = services.BuildServiceProvider(); - _container.UseDUI(false); + _container.UseDUI(); RevitTask.Initialize(application); RevitEvents.Register(_container.GetRequiredService(), application); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems index 485644d2e..615ffb90e 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems @@ -44,7 +44,7 @@ - + From 6e1a0225eb34be3a2d869dd9b28ac96496d6fc2c Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 23 Jan 2025 11:15:49 +0000 Subject: [PATCH 11/12] formatting --- .../Bindings/RevitSendBinding.cs | 4 +--- .../HostApp/RevitDocumentStore.cs | 13 +++---------- .../Plugin/RevitEvents.cs | 8 ++------ .../Eventing/EventAggregatorTests.cs | 8 +++++--- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index 07e738bf5..e16ae8d28 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -269,9 +269,7 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs _sendConversionCache.EvictObjects(unpackedObjectIds); } - _eventAggregator - .GetEvent() - .OneTimeSubscribe(nameof(CheckFilterExpiration), CheckFilterExpiration); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(CheckFilterExpiration), CheckFilterExpiration); _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RunExpirationChecks), RunExpirationChecks); } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index 8c96692e7..ceedd2d3a 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -42,10 +42,8 @@ IEventAggregator eventAggregator // It is kind of harmless since we create this object as "SingleInstance". LoadState(); } - - - private void OnDocumentOpen(object _) => - IsDocumentInit = false; + + private void OnDocumentOpen(object _) => IsDocumentInit = false; public override Task OnDocumentStoreInitialized() => _eventAggregator.GetEvent().PublishAsync(new object()); @@ -67,12 +65,7 @@ private void OnViewActivated(ViewActivatedEventArgs e) } IsDocumentInit = true; - _eventAggregator - .GetEvent() - .OneTimeSubscribe( - nameof(RevitDocumentStore), - OnIdleEvent - ); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RevitDocumentStore), OnIdleEvent); } private async Task OnIdleEvent(object _) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs index 374d0abae..7d5add3b1 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs @@ -15,6 +15,7 @@ public class ViewActivatedEvent(IThreadContext threadContext, ITopLevelException public class DocumentOpeningEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); + public class DocumentOpenedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); @@ -59,12 +60,7 @@ await eventAggregator #else application.SelectionChanged += (_, _) => - eventAggregator - .GetEvent() - .OneTimeSubscribe( - "Selection", - OnSelectionChanged - ); + eventAggregator.GetEvent().OneTimeSubscribe("Selection", OnSelectionChanged); #endif } diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs index 6efd5cea6..5271700d3 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs @@ -113,7 +113,7 @@ public async Task Sub_Sync() GC.WaitForPendingFinalizers(); subscriptionToken.IsActive.Should().BeFalse(); } - + private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); @@ -121,7 +121,6 @@ private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) return subscriptionToken; } - [Test] public async Task Sub_Sync_Static() { @@ -151,18 +150,19 @@ public async Task Sub_Sync_Static() GC.WaitForPendingFinalizers(); subscriptionToken.IsActive.Should().BeFalse(); } + private static SubscriptionToken Test_Sub_Sync_Static(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestSyncStaticSubscribe); return subscriptionToken; } + private static void OnTestSyncStaticSubscribe(object _) { s_val = true; } - [Test] public async Task Sub_Async_Static() { @@ -192,12 +192,14 @@ public async Task Sub_Async_Static() GC.WaitForPendingFinalizers(); subscriptionToken.IsActive.Should().BeFalse(); } + private static SubscriptionToken Test_Sub_Async_Static(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestAsyncStaticSubscribe); return subscriptionToken; } + private static Task OnTestAsyncStaticSubscribe(object _) { s_val = true; From 08e89d350be8eb22cf5b3a094c7d7a5d81403205 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 24 Jan 2025 11:55:21 +0000 Subject: [PATCH 12/12] Change publish to avoid reentrancy issues. Change from semaphore to lock statement --- .../Eventing/OneTimeThreadedEvent.cs | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs index 4e5e0de23..3f6a8a276 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs @@ -4,37 +4,18 @@ namespace Speckle.Connectors.DUI.Eventing; public abstract class OneTimeThreadedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) - : SpeckleEvent(threadContext, exceptionHandler), - IDisposable + : SpeckleEvent(threadContext, exceptionHandler) where T : notnull { - private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Dictionary _activeTokens = new(); - protected virtual void Dispose(bool isDisposing) - { - if (isDisposing) - { - _semaphore.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~OneTimeThreadedEvent() => Dispose(false); - public SubscriptionToken OneTimeSubscribe( string id, Func action, ThreadOption threadOption = ThreadOption.PublisherThread ) { - _semaphore.Wait(); - try + lock (_activeTokens) { if (_activeTokens.TryGetValue(id, out var token)) { @@ -44,14 +25,10 @@ public SubscriptionToken OneTimeSubscribe( } _activeTokens.Remove(id); } - token = Subscribe(action, threadOption, EventFeatures.OneTime); + token = Subscribe(action, threadOption, EventFeatures.OneTime | EventFeatures.IsAsync); _activeTokens.Add(id, token); return token; } - finally - { - _semaphore.Release(); - } } public SubscriptionToken OneTimeSubscribe( @@ -60,8 +37,7 @@ public SubscriptionToken OneTimeSubscribe( ThreadOption threadOption = ThreadOption.PublisherThread ) { - _semaphore.Wait(); - try + lock (_activeTokens) { if (_activeTokens.TryGetValue(id, out var token)) { @@ -75,28 +51,26 @@ public SubscriptionToken OneTimeSubscribe( _activeTokens.Add(id, token); return token; } - finally - { - _semaphore.Release(); - } } public override async Task PublishAsync(T payload) { - await _semaphore.WaitAsync(); - try + SubscriptionToken[] tokensToDestory = []; + lock (_activeTokens) { - await base.PublishAsync(payload); - foreach (var token in _activeTokens.Values) + if (_activeTokens.Count > 0) { - token.Dispose(); + tokensToDestory = _activeTokens.Values.ToArray(); + _activeTokens.Clear(); } - - _activeTokens.Clear(); } - finally + await base.PublishAsync(payload); + if (tokensToDestory.Length > 0) { - _semaphore.Release(); + foreach (var token in tokensToDestory) + { + token.Dispose(); + } } } }