diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index 781b0a046..9a2db88a4 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 ) 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 95438ad70..e16ae8d28 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,13 +78,13 @@ 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().Subscribe(OnDocumentStoreChangedEvent); } @@ -91,8 +93,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() => @@ -111,7 +113,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."); @@ -171,12 +173,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(); @@ -247,13 +249,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) { @@ -267,8 +269,8 @@ 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 @@ -305,9 +307,9 @@ private bool HaveUnitsChanged(Document doc) return false; } - private async Task PostSetObjectIds() + private async Task PostSetObjectIds(object _) { - foreach (var sender in Store.GetSenders().ToList()) + foreach (var sender in _store.GetSenders().ToList()) { await RefreshElementsOnSender(sender); } @@ -316,7 +318,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); @@ -327,17 +329,17 @@ 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(); } } - private async Task RunExpirationChecks() + private async Task RunExpirationChecks(object _) { - 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) { @@ -388,7 +390,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..6c194ce67 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() + private void OnSelectionChanged(object _) { - 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 6647f8b63..ceedd2d3a 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,51 +15,43 @@ 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(OnDocumentOpen); + eventAggregator.GetEvent().Subscribe(OnDocumentOpen); + 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(); } + private void OnDocumentOpen(object _) => IsDocumentInit = false; + public override Task OnDocumentStoreInitialized() => _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 +65,13 @@ 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), OnIdleEvent); + } + + private async Task OnIdleEvent(object _) + { + 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/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/RevitEvents.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs new file mode 100644 index 000000000..7d5add3b1 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitEvents.cs @@ -0,0 +1,77 @@ +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 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) + : 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); +#else + private static IEventAggregator? s_eventAggregator; +#endif + + 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 + .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", OnSelectionChanged); +#endif + } + +#if !REVIT2022 + private static async Task OnSelectionChanged(object _) + { + if (s_eventAggregator is null) + { + return; + } + 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 8f1a178f9..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(false); + _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..615ffb90e 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/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs index c3c3e3200..050849193 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(OnSelectionChangeEvent); } 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 7d20e3ea8..5271700d3 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(); @@ -92,7 +122,7 @@ private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) } [Test] - public async Task Sub_Sync() + public async Task Sub_Sync_Static() { s_val = false; var services = new ServiceCollection(); @@ -107,7 +137,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()); @@ -121,13 +151,61 @@ public async Task Sub_Sync() subscriptionToken.IsActive.Should().BeFalse(); } - private SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) + private static SubscriptionToken Test_Sub_Sync_Static(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestAsyncSubscribe); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestSyncStaticSubscribe); return subscriptionToken; } + private static void OnTestSyncStaticSubscribe(object _) + { + s_val = true; + } + + [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(); + 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 _) { s_val = true; @@ -169,6 +247,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 b3a63831d..153b224e8 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; @@ -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; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs index 9e5b719c1..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; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs index 75e41531b..3f6a8a276 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/OneTimeThreadedEvent.cs @@ -1,40 +1,21 @@ -using Speckle.Connectors.Common.Threading; +using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; 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(); + } } } } 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;