diff --git a/All.sln.DotSettings b/All.sln.DotSettings
index a68eee20a7..c5930b608d 100644
--- a/All.sln.DotSettings
+++ b/All.sln.DotSettings
@@ -625,6 +625,7 @@
QL
SQ
UI
+ URI
True
ExternalToolData|CSharpier|csharpier||csharpier|$FILE$
CamelCase
diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs
index 157bad2797..a9c84e14f5 100644
--- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs
+++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs
@@ -10,16 +10,16 @@ public class ArcGISSelectionBinding : ISelectionBinding
public string Name => "selectionBinding";
public IBridge Parent { get; }
- public ArcGISSelectionBinding(IBridge parent)
+ public ArcGISSelectionBinding(IBridge parent, ITopLevelExceptionHandler topLevelHandler)
{
Parent = parent;
// example: https://github.com/Esri/arcgis-pro-sdk-community-samples/blob/master/Map-Authoring/QueryBuilderControl/DefinitionQueryDockPaneViewModel.cs
// MapViewEventArgs args = new(MapView.Active);
- TOCSelectionChangedEvent.Subscribe(OnSelectionChanged, true);
+ TOCSelectionChangedEvent.Subscribe(_ => topLevelHandler.CatchUnhandled(OnSelectionChanged), true);
}
- private void OnSelectionChanged(MapViewEventArgs args)
+ private void OnSelectionChanged()
{
SelectionInfo selInfo = GetSelection();
Parent.Send(SelectionBindingEvents.SET_SELECTION, selInfo);
diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs
index ef05b43288..4c0215694b 100644
--- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs
+++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs
@@ -31,6 +31,7 @@ public sealed class ArcGISSendBinding : ISendBinding
private readonly List _sendFilters;
private readonly CancellationManager _cancellationManager;
private readonly ISendConversionCache _sendConversionCache;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
///
/// Used internally to aggregate the changed objects' id.
@@ -45,7 +46,8 @@ public ArcGISSendBinding(
IEnumerable sendFilters,
IUnitOfWorkFactory unitOfWorkFactory,
CancellationManager cancellationManager,
- ISendConversionCache sendConversionCache
+ ISendConversionCache sendConversionCache,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
{
_store = store;
@@ -53,6 +55,7 @@ ISendConversionCache sendConversionCache
_sendFilters = sendFilters.ToList();
_cancellationManager = cancellationManager;
_sendConversionCache = sendConversionCache;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
Commands = new SendBindingUICommands(parent);
SubscribeToArcGISEvents();
@@ -60,17 +63,40 @@ ISendConversionCache sendConversionCache
private void SubscribeToArcGISEvents()
{
- LayersRemovedEvent.Subscribe(GetIdsForLayersRemovedEvent, true);
- StandaloneTablesRemovedEvent.Subscribe(GetIdsForStandaloneTablesRemovedEvent, true);
- MapPropertyChangedEvent.Subscribe(GetIdsForMapPropertyChangedEvent, true); // Map units, CRS etc.
- MapMemberPropertiesChangedEvent.Subscribe(GetIdsForMapMemberPropertiesChangedEvent, true); // e.g. Layer name
-
- ActiveMapViewChangedEvent.Subscribe(SubscribeToMapMembersDataSourceChange, true);
- LayersAddedEvent.Subscribe(GetIdsForLayersAddedEvent, true);
- StandaloneTablesAddedEvent.Subscribe(GetIdsForStandaloneTablesAddedEvent, true);
+ LayersRemovedEvent.Subscribe(
+ a => _topLevelExceptionHandler.CatchUnhandled(() => GetIdsForLayersRemovedEvent(a)),
+ true
+ );
+
+ StandaloneTablesRemovedEvent.Subscribe(
+ a => _topLevelExceptionHandler.CatchUnhandled(() => GetIdsForStandaloneTablesRemovedEvent(a)),
+ true
+ );
+
+ MapPropertyChangedEvent.Subscribe(
+ a => _topLevelExceptionHandler.CatchUnhandled(() => GetIdsForMapPropertyChangedEvent(a)),
+ true
+ ); // Map units, CRS etc.
+
+ MapMemberPropertiesChangedEvent.Subscribe(
+ a => _topLevelExceptionHandler.CatchUnhandled(() => GetIdsForMapMemberPropertiesChangedEvent(a)),
+ true
+ ); // e.g. Layer name
+
+ ActiveMapViewChangedEvent.Subscribe(
+ _ => _topLevelExceptionHandler.CatchUnhandled(SubscribeToMapMembersDataSourceChange),
+ true
+ );
+
+ LayersAddedEvent.Subscribe(a => _topLevelExceptionHandler.CatchUnhandled(() => GetIdsForLayersAddedEvent(a)), true);
+
+ StandaloneTablesAddedEvent.Subscribe(
+ a => _topLevelExceptionHandler.CatchUnhandled(() => GetIdsForStandaloneTablesAddedEvent(a)),
+ true
+ );
}
- private void SubscribeToMapMembersDataSourceChange(ActiveMapViewChangedEventArgs args)
+ private void SubscribeToMapMembersDataSourceChange()
{
var task = QueuedTask.Run(() =>
{
diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs
index d8ca75f959..db0df3550f 100644
--- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs
+++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs
@@ -3,6 +3,7 @@
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Mapping.Events;
+using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.Utils;
using Speckle.Newtonsoft.Json;
@@ -11,34 +12,41 @@ namespace Speckle.Connectors.ArcGIS.Utils;
public class ArcGISDocumentStore : DocumentModelStore
{
- public ArcGISDocumentStore(JsonSerializerSettings serializerOption)
+ public ArcGISDocumentStore(
+ JsonSerializerSettings serializerOption,
+ ITopLevelExceptionHandler topLevelExceptionHandler
+ )
: base(serializerOption, true)
{
- ActiveMapViewChangedEvent.Subscribe(OnMapViewChanged);
- ProjectSavingEvent.Subscribe(OnProjectSaving);
- ProjectClosingEvent.Subscribe(OnProjectClosing);
+ ActiveMapViewChangedEvent.Subscribe(a => topLevelExceptionHandler.CatchUnhandled(() => OnMapViewChanged(a)));
+ ProjectSavingEvent.Subscribe(_ =>
+ {
+ topLevelExceptionHandler.CatchUnhandled(OnProjectSaving);
+ return Task.CompletedTask;
+ });
+ ProjectClosingEvent.Subscribe(_ =>
+ {
+ topLevelExceptionHandler.CatchUnhandled(OnProjectClosing);
+ return Task.CompletedTask;
+ });
}
- private Task OnProjectClosing(ProjectClosingEventArgs arg)
+ private void OnProjectClosing()
{
if (MapView.Active is null)
{
- return Task.CompletedTask;
+ return;
}
WriteToFile();
- return Task.CompletedTask;
}
- private Task OnProjectSaving(ProjectEventArgs arg)
+ private void OnProjectSaving()
{
- if (MapView.Active is null)
+ if (MapView.Active is not null)
{
- return Task.CompletedTask;
+ WriteToFile();
}
-
- WriteToFile();
- return Task.CompletedTask;
}
///
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs
index 1da7250ceb..1e13090a9f 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs
@@ -8,20 +8,24 @@ namespace Speckle.Connectors.Autocad.Bindings;
public class AutocadSelectionBinding : ISelectionBinding
{
private const string SELECTION_EVENT = "setSelection";
-
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly HashSet _visitedDocuments = new();
+
public string Name => "selectionBinding";
+
public IBridge Parent { get; }
- public AutocadSelectionBinding(IBridge parent)
+ public AutocadSelectionBinding(IBridge parent, ITopLevelExceptionHandler topLevelExceptionHandler)
{
+ _topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
// POC: Use here Context for doc. In converters it's OK but we are still lacking to use context into bindings.
// It is with the case of if binding created with already a document
// This is valid when user opens acad file directly double clicking
TryRegisterDocumentForSelection(Application.DocumentManager.MdiActiveDocument);
- Application.DocumentManager.DocumentActivated += (sender, e) => OnDocumentChanged(e.Document);
+ Application.DocumentManager.DocumentActivated += (_, e) =>
+ _topLevelExceptionHandler.CatchUnhandled(() => OnDocumentChanged(e.Document));
}
private void OnDocumentChanged(Document? document) => TryRegisterDocumentForSelection(document);
@@ -36,9 +40,7 @@ private void TryRegisterDocumentForSelection(Document? document)
if (!_visitedDocuments.Contains(document))
{
document.ImpliedSelectionChanged += (_, _) =>
- {
- Parent.RunOnMainThread(OnSelectionChanged);
- };
+ _topLevelExceptionHandler.CatchUnhandled(() => Parent.RunOnMainThread(OnSelectionChanged));
_visitedDocuments.Add(document);
}
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs
index 4d580c6aed..32cc40fbe7 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs
@@ -29,6 +29,7 @@ public sealed class AutocadSendBinding : ISendBinding
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
private readonly AutocadSettings _autocadSettings;
private readonly ISendConversionCache _sendConversionCache;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
///
/// Used internally to aggregate the changed objects' id.
@@ -43,7 +44,8 @@ public AutocadSendBinding(
CancellationManager cancellationManager,
AutocadSettings autocadSettings,
IUnitOfWorkFactory unitOfWorkFactory,
- ISendConversionCache sendConversionCache
+ ISendConversionCache sendConversionCache,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
{
_store = store;
@@ -53,10 +55,13 @@ ISendConversionCache sendConversionCache
_cancellationManager = cancellationManager;
_sendFilters = sendFilters.ToList();
_sendConversionCache = sendConversionCache;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
Commands = new SendBindingUICommands(parent);
- Application.DocumentManager.DocumentActivated += (sender, args) => SubscribeToObjectChanges(args.Document);
+ Application.DocumentManager.DocumentActivated += (_, args) =>
+ topLevelExceptionHandler.CatchUnhandled(() => SubscribeToObjectChanges(args.Document));
+
if (Application.DocumentManager.CurrentDocument != null)
{
// catches the case when autocad just opens up with a blank new doc
@@ -74,9 +79,14 @@ private void SubscribeToObjectChanges(Document doc)
}
_docSubsTracker.Add(doc.Name);
- doc.Database.ObjectAppended += (_, e) => OnChangeChangedObjectIds(e.DBObject);
- doc.Database.ObjectErased += (_, e) => OnChangeChangedObjectIds(e.DBObject);
- doc.Database.ObjectModified += (_, e) => OnChangeChangedObjectIds(e.DBObject);
+ doc.Database.ObjectAppended += (_, e) => OnObjectChanged(e.DBObject);
+ doc.Database.ObjectErased += (_, e) => OnObjectChanged(e.DBObject);
+ doc.Database.ObjectModified += (_, e) => OnObjectChanged(e.DBObject);
+ }
+
+ void OnObjectChanged(DBObject dbObject)
+ {
+ _topLevelExceptionHandler.CatchUnhandled(() => OnChangeChangedObjectIds(dbObject));
}
private void OnChangeChangedObjectIds(DBObject dBObject)
diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs
index 17ebfccee0..a01b4a0716 100644
--- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs
+++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs
@@ -1,3 +1,4 @@
+using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.Utils;
using Speckle.Newtonsoft.Json;
@@ -12,7 +13,8 @@ public class AutocadDocumentStore : DocumentModelStore
public AutocadDocumentStore(
JsonSerializerSettings jsonSerializerSettings,
- AutocadDocumentManager autocadDocumentManager
+ AutocadDocumentManager autocadDocumentManager,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(jsonSerializerSettings, true)
{
@@ -29,14 +31,15 @@ AutocadDocumentManager autocadDocumentManager
OnDocChangeInternal(Application.DocumentManager.MdiActiveDocument);
}
- Application.DocumentManager.DocumentActivated += (_, e) => OnDocChangeInternal(e.Document);
+ Application.DocumentManager.DocumentActivated += (_, e) =>
+ topLevelExceptionHandler.CatchUnhandled(() => OnDocChangeInternal(e.Document));
// since below event triggered as secondary, it breaks the logic in OnDocChangeInternal function, leaving it here for now.
// Autodesk.AutoCAD.ApplicationServices.Application.DocumentWindowCollection.DocumentWindowActivated += (_, args) =>
// OnDocChangeInternal((Document)args.DocumentWindow.Document);
}
- private void OnDocChangeInternal(Document doc)
+ private void OnDocChangeInternal(Document? doc)
{
var currentDocName = doc != null ? doc.Name : _nullDocumentName;
if (_previousDocName == currentDocName)
@@ -54,7 +57,7 @@ public override void ReadFromFile()
Models = new();
// POC: Will be addressed to move it into AutocadContext!
- Document doc = Application.DocumentManager.MdiActiveDocument;
+ Document? doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null)
{
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs
index ab60430881..8bd3033442 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs
@@ -27,6 +27,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly CancellationManager _cancellationManager;
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
private readonly ISendConversionCache _sendConversionCache;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public RevitSendBinding(
IRevitIdleManager idleManager,
@@ -36,7 +37,8 @@ public RevitSendBinding(
IBridge bridge,
IUnitOfWorkFactory unitOfWorkFactory,
RevitSettings revitSettings,
- ISendConversionCache sendConversionCache
+ ISendConversionCache sendConversionCache,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
: base("sendBinding", store, bridge, revitContext)
{
@@ -45,12 +47,15 @@ ISendConversionCache sendConversionCache
_unitOfWorkFactory = unitOfWorkFactory;
_revitSettings = revitSettings;
_sendConversionCache = sendConversionCache;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
Commands = new SendBindingUICommands(bridge);
// TODO expiry events
// TODO filters need refresh events
- revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) => DocChangeHandler(e);
- Store.DocumentChanged += (_, _) => OnDocumentChanged();
+ revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
+ _topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
+
+ Store.DocumentChanged += (_, _) => _topLevelExceptionHandler.CatchUnhandled(OnDocumentChanged);
}
public List GetSendFilters()
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs
index 11b7e8e864..d60fe3ef27 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SelectionBinding.cs
@@ -11,20 +11,23 @@ namespace Speckle.Connectors.Revit.Bindings;
internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding
{
private readonly IRevitIdleManager _revitIdleManager;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public SelectionBinding(
RevitContext revitContext,
DocumentModelStore store,
IRevitIdleManager idleManager,
- IBridge bridge
+ IBridge bridge,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
: base("selectionBinding", store, bridge, revitContext)
{
_revitIdleManager = idleManager;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
// POC: we can inject the solution here
// TODO: Need to figure it out equivalent of SelectionChanged for Revit2020
RevitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
- _revitIdleManager.SubscribeToIdle(OnSelectionChanged);
+ topLevelExceptionHandler.CatchUnhandled(() => _revitIdleManager.SubscribeToIdle(OnSelectionChanged));
}
private void OnSelectionChanged()
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs
index 1278431790..ba9b7d8db3 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs
@@ -4,6 +4,7 @@
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using Revit.Async;
+using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Connectors.RevitShared.Helpers;
@@ -29,7 +30,8 @@ public RevitDocumentStore(
RevitContext revitContext,
JsonSerializerSettings serializerSettings,
DocumentModelStorageSchema documentModelStorageSchema,
- IdStorageSchema idStorageSchema
+ IdStorageSchema idStorageSchema,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
: base(serializerSettings, true)
{
@@ -40,12 +42,15 @@ IdStorageSchema idStorageSchema
UIApplication uiApplication = _revitContext.UIApplication.NotNull();
- uiApplication.ViewActivated += OnViewActivated;
+ uiApplication.ViewActivated += (s, e) => topLevelExceptionHandler.CatchUnhandled(() => OnViewActivated(s, e));
- uiApplication.Application.DocumentOpening += (_, _) => IsDocumentInit = false;
- uiApplication.Application.DocumentOpened += (_, _) => IsDocumentInit = false;
+ uiApplication.Application.DocumentOpening += (_, _) =>
+ topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
- Models.CollectionChanged += (_, _) => WriteToFile();
+ uiApplication.Application.DocumentOpened += (_, _) =>
+ topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
+
+ Models.CollectionChanged += (_, _) => topLevelExceptionHandler.CatchUnhandled(WriteToFile);
// 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".
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs
index 8456ded287..6e3d0c4e5e 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
+using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.RevitShared.Helpers;
namespace Speckle.Connectors.Revit.Plugin;
@@ -9,6 +10,7 @@ namespace Speckle.Connectors.Revit.Plugin;
// is probably misnamed, perhaps OnIdleCallbackManager
internal sealed class RevitIdleManager : IRevitIdleManager
{
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly UIApplication _uiApplication;
private readonly ConcurrentDictionary _calls = new();
@@ -16,8 +18,9 @@ internal sealed class RevitIdleManager : IRevitIdleManager
// POC: still not thread safe
private volatile bool _hasSubscribed;
- public RevitIdleManager(RevitContext revitContext)
+ public RevitIdleManager(RevitContext revitContext, ITopLevelExceptionHandler topLevelExceptionHandler)
{
+ _topLevelExceptionHandler = topLevelExceptionHandler;
_uiApplication = revitContext.UIApplication!;
}
@@ -46,15 +49,18 @@ public void SubscribeToIdle(Action action)
private void RevitAppOnIdle(object sender, IdlingEventArgs e)
{
- foreach (KeyValuePair kvp in _calls)
+ _topLevelExceptionHandler.CatchUnhandled(() =>
{
- kvp.Value();
- }
+ foreach (KeyValuePair kvp in _calls)
+ {
+ kvp.Value.Invoke();
+ }
- _calls.Clear();
- _uiApplication.Idling -= RevitAppOnIdle;
+ _calls.Clear();
+ _uiApplication.Idling -= RevitAppOnIdle;
- // setting last will delay ntering re-subscritption
- _hasSubscribed = false;
+ // setting last will delay ntering re-subscritption
+ _hasSubscribed = false;
+ });
}
}
diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs
index 077a4df2fd..ded20ed05f 100644
--- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs
+++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs
@@ -8,33 +8,37 @@ namespace Speckle.Connectors.Rhino7.Bindings;
public class RhinoSelectionBinding : ISelectionBinding
{
+ private readonly RhinoIdleManager _idleManager;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private const string SELECTION_EVENT = "setSelection";
- public string Name { get; } = "selectionBinding";
- public IBridge Parent { get; set; }
+ public string Name => "selectionBinding";
+ public IBridge Parent { get; }
- public RhinoSelectionBinding(RhinoIdleManager idleManager, IBridge parent)
+ public RhinoSelectionBinding(
+ RhinoIdleManager idleManager,
+ IBridge parent,
+ ITopLevelExceptionHandler topLevelExceptionHandler
+ )
{
+ _idleManager = idleManager;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
- RhinoDoc.SelectObjects += (_, _) =>
- {
- idleManager.SubscribeToIdle(OnSelectionChanged);
- };
- RhinoDoc.DeselectObjects += (_, _) =>
- {
- idleManager.SubscribeToIdle(OnSelectionChanged);
- };
- RhinoDoc.DeselectAllObjects += (_, _) =>
- {
- idleManager.SubscribeToIdle(OnSelectionChanged);
- };
+ RhinoDoc.SelectObjects += OnSelectionChange;
+ RhinoDoc.DeselectObjects += OnSelectionChange;
+ RhinoDoc.DeselectAllObjects += OnSelectionChange;
}
- private void OnSelectionChanged()
+ void OnSelectionChange(object o, EventArgs eventArgs)
+ {
+ _idleManager.SubscribeToIdle(() => _topLevelExceptionHandler.CatchUnhandled(UpdateSelection));
+ }
+
+ private void UpdateSelection()
{
SelectionInfo selInfo = GetSelection();
- Parent?.Send(SELECTION_EVENT, selInfo);
+ Parent.Send(SELECTION_EVENT, selInfo);
}
public SelectionInfo GetSelection()
diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs
index b2c2ab1a20..96ed6aa8a0 100644
--- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs
+++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs
@@ -36,6 +36,7 @@ public sealed class RhinoSendBinding : ISendBinding
private HashSet ChangedObjectIds { get; set; } = new();
private readonly ISendConversionCache _sendConversionCache;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
public RhinoSendBinding(
DocumentModelStore store,
@@ -46,7 +47,8 @@ public RhinoSendBinding(
IUnitOfWorkFactory unitOfWorkFactory,
RhinoSettings rhinoSettings,
CancellationManager cancellationManager,
- ISendConversionCache sendConversionCache
+ ISendConversionCache sendConversionCache,
+ ITopLevelExceptionHandler topLevelExceptionHandler
)
{
_store = store;
@@ -57,6 +59,7 @@ ISendConversionCache sendConversionCache
_rhinoSettings = rhinoSettings;
_cancellationManager = cancellationManager;
_sendConversionCache = sendConversionCache;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
Commands = new SendBindingUICommands(parent); // POC: Commands are tightly coupled with their bindings, at least for now, saves us injecting a factory.
SubscribeToRhinoEvents();
@@ -64,48 +67,45 @@ ISendConversionCache sendConversionCache
private void SubscribeToRhinoEvents()
{
- // POC: It is unclear to me why is the binding keeping track of ChangedObjectIds. Change tracking should be moved to a separate type.
- RhinoDoc.LayerTableEvent += (_, _) =>
- {
- Commands.RefreshSendFilters();
- };
-
RhinoDoc.AddRhinoObject += (_, e) =>
- {
- // NOTE: This does not work if rhino starts and opens a blank doc;
- if (!_store.IsDocumentInit)
+ _topLevelExceptionHandler.CatchUnhandled(() =>
{
- return;
- }
+ // NOTE: This does not work if rhino starts and opens a blank doc;
+ if (!_store.IsDocumentInit)
+ {
+ return;
+ }
- ChangedObjectIds.Add(e.ObjectId.ToString());
- _idleManager.SubscribeToIdle(RunExpirationChecks);
- };
+ ChangedObjectIds.Add(e.ObjectId.ToString());
+ _idleManager.SubscribeToIdle(RunExpirationChecks);
+ });
RhinoDoc.DeleteRhinoObject += (_, e) =>
- {
- // NOTE: This does not work if rhino starts and opens a blank doc;
- if (!_store.IsDocumentInit)
+ _topLevelExceptionHandler.CatchUnhandled(() =>
{
- return;
- }
+ // NOTE: This does not work if rhino starts and opens a blank doc;
+ if (!_store.IsDocumentInit)
+ {
+ return;
+ }
- ChangedObjectIds.Add(e.ObjectId.ToString());
- _idleManager.SubscribeToIdle(RunExpirationChecks);
- };
+ ChangedObjectIds.Add(e.ObjectId.ToString());
+ _idleManager.SubscribeToIdle(RunExpirationChecks);
+ });
RhinoDoc.ReplaceRhinoObject += (_, e) =>
- {
- // NOTE: This does not work if rhino starts and opens a blank doc;
- if (!_store.IsDocumentInit)
+ _topLevelExceptionHandler.CatchUnhandled(() =>
{
- return;
- }
-
- ChangedObjectIds.Add(e.NewRhinoObject.Id.ToString());
- ChangedObjectIds.Add(e.OldRhinoObject.Id.ToString());
- _idleManager.SubscribeToIdle(RunExpirationChecks);
- };
+ // NOTE: This does not work if rhino starts and opens a blank doc;
+ if (!_store.IsDocumentInit)
+ {
+ return;
+ }
+
+ ChangedObjectIds.Add(e.NewRhinoObject.Id.ToString());
+ ChangedObjectIds.Add(e.OldRhinoObject.Id.ToString());
+ _idleManager.SubscribeToIdle(RunExpirationChecks);
+ });
}
public List GetSendFilters() => _sendFilters;
diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs
index 2545b44089..40ac283bce 100644
--- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs
+++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs
@@ -1,4 +1,5 @@
using Rhino;
+using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Newtonsoft.Json;
@@ -6,29 +7,35 @@ namespace Speckle.Connectors.Rhino7.HostApp;
public class RhinoDocumentStore : DocumentModelStore
{
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
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(JsonSerializerSettings jsonSerializerSettings)
+ public RhinoDocumentStore(
+ JsonSerializerSettings jsonSerializerSettings,
+ ITopLevelExceptionHandler topLevelExceptionHandler
+ )
: base(jsonSerializerSettings, true)
{
- RhinoDoc.BeginOpenDocument += (_, _) => IsDocumentInit = false;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
+ RhinoDoc.BeginOpenDocument += (_, _) => topLevelExceptionHandler.CatchUnhandled(() => IsDocumentInit = false);
RhinoDoc.EndOpenDocument += (_, e) =>
- {
- if (e.Merge)
+ topLevelExceptionHandler.CatchUnhandled(() =>
{
- return;
- }
+ if (e.Merge)
+ {
+ return;
+ }
- if (e.Document == null)
- {
- return;
- }
+ if (e.Document == null)
+ {
+ return;
+ }
- IsDocumentInit = true;
- ReadFromFile();
- OnDocumentChanged();
- };
+ IsDocumentInit = true;
+ ReadFromFile();
+ OnDocumentChanged();
+ });
}
public override void WriteToFile()
@@ -38,10 +45,10 @@ public override void WriteToFile()
return; // Should throw
}
- RhinoDoc.ActiveDoc?.Strings.Delete(SPECKLE_KEY);
+ RhinoDoc.ActiveDoc.Strings.Delete(SPECKLE_KEY);
string serializedState = Serialize();
- RhinoDoc.ActiveDoc?.Strings.SetString(SPECKLE_KEY, SPECKLE_KEY, serializedState);
+ RhinoDoc.ActiveDoc.Strings.SetString(SPECKLE_KEY, SPECKLE_KEY, serializedState);
}
public override void ReadFromFile()
diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs
index 4e9cc6bcc5..c3153a0d5f 100644
--- a/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs
+++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs
@@ -2,18 +2,23 @@
using System.Windows.Threading;
using Microsoft.Web.WebView2.Core;
using Speckle.Connectors.DUI.Bindings;
+using Speckle.Connectors.DUI.Bridge;
namespace Speckle.Connectors.DUI.WebView;
public sealed partial class DUI3ControlWebView : UserControl
{
private readonly IEnumerable> _bindings;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
- public DUI3ControlWebView(IEnumerable> bindings)
+ public DUI3ControlWebView(IEnumerable> bindings, ITopLevelExceptionHandler topLevelExceptionHandler)
{
_bindings = bindings;
+ _topLevelExceptionHandler = topLevelExceptionHandler;
InitializeComponent();
- Browser.CoreWebView2InitializationCompleted += OnInitialized;
+
+ Browser.CoreWebView2InitializationCompleted += (sender, args) =>
+ _topLevelExceptionHandler.CatchUnhandled(() => OnInitialized(sender, args));
}
private void ShowDevToolsMethod() => Browser.CoreWebView2.OpenDevToolsWindow();
@@ -30,9 +35,9 @@ private void ExecuteScriptAsyncMethod(string script)
private void OnInitialized(object? sender, CoreWebView2InitializationCompletedEventArgs e)
{
- if (e.IsSuccess == false)
+ if (!e.IsSuccess)
{
- //POC: avoid silently accepting webview failures handle...
+ throw new InvalidOperationException("Webview Failed to initialize", e.InitializationException);
}
// We use Lazy here to delay creating the binding until after the Browser is fully initialized.
diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs
index 966be49fe2..7b38ee9fbd 100644
--- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs
+++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs
@@ -45,7 +45,7 @@ public class BasicConnectorBindingCommands
private const string NOTIFY_DOCUMENT_CHANGED_EVENT_NAME = "documentChanged";
private const string SET_MODEL_PROGRESS_UI_COMMAND_NAME = "setModelProgress";
private const string SET_MODEL_ERROR_UI_COMMAND_NAME = "setModelError";
- private const string SET_GLOBAL_NOTIFICATION = "setGlobalNotification";
+ internal const string SET_GLOBAL_NOTIFICATION = "setGlobalNotification";
protected IBridge Bridge { get; }
diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs
index 2c27fccc38..aa26374558 100644
--- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs
+++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs
@@ -1,7 +1,7 @@
+using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.InteropServices;
using Speckle.Newtonsoft.Json;
-using Speckle.Core.Logging;
using Speckle.Connectors.DUI.Bindings;
using System.Threading.Tasks.Dataflow;
using System.Diagnostics;
@@ -18,7 +18,7 @@ namespace Speckle.Connectors.DUI.Bridge;
///
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
-public class BrowserBridge : IBridge
+public sealed class BrowserBridge : IBridge
{
///
/// The name under which we expect the frontend to hoist this bindings class to the global scope.
@@ -26,17 +26,19 @@ public class BrowserBridge : IBridge
///
private readonly JsonSerializerSettings _serializerOptions;
- private readonly Dictionary _resultsStore = new();
+ private readonly ConcurrentDictionary _resultsStore = new();
private readonly SynchronizationContext _mainThreadContext;
+ private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
+
+ private IReadOnlyDictionary _bindingMethodCache = new Dictionary();
- private Dictionary BindingMethodCache { get; set; } = new();
private ActionBlock? _actionBlock;
private Action? _scriptMethod;
private IBinding? _binding;
private Type? _bindingType;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
///
/// Action that opens up the developer tools of the respective browser we're using. While webview2 allows for "right click, inspect", cefsharp does not - hence the need for this.
@@ -77,7 +79,7 @@ public BrowserBridge(JsonSerializerSettings jsonSerializerSettings, ILoggerFacto
{
_serializerOptions = jsonSerializerSettings;
_logger = loggerFactory.CreateLogger();
-
+ _topLevelExceptionHandler = new TopLevelExceptionHandler(loggerFactory, this); //TODO: Probably we could inject this with a Lazy somewhere
// Capture the main thread's SynchronizationContext
_mainThreadContext = SynchronizationContext.Current;
}
@@ -96,38 +98,51 @@ Action showDevToolsAction
_scriptMethod = scriptMethod;
_bindingType = binding.GetType();
- BindingMethodCache = new Dictionary();
ShowDevToolsAction = showDevToolsAction;
// Note: we need to filter out getter and setter methods here because they are not really nicely
// supported across browsers, hence the !method.IsSpecialName.
+ var bindingMethodCache = new Dictionary();
foreach (var m in _bindingType.GetMethods().Where(method => !method.IsSpecialName))
{
- BindingMethodCache[m.Name] = m;
+ bindingMethodCache[m.Name] = m;
}
+ _bindingMethodCache = bindingMethodCache;
// Whenever the ui will call run method inside .net, it will post a message to this action block.
// This conveniently executes the code outside the UI thread and does not block during long operations (such as sending).
- // POC: I wonder if TL exception handler should be living here...
_actionBlock = new ActionBlock(
- args => ExecuteMethod(args.MethodName, args.RequestId, args.MethodArgs),
+ OnActionBlock,
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1000,
- CancellationToken = new CancellationTokenSource(TimeSpan.FromHours(3)).Token // Not sure we need such a long time.
+ CancellationToken = new CancellationTokenSource(TimeSpan.FromHours(3)).Token // Not sure we need such a long time. //TODO: This token source is not disposed....
}
);
_logger.LogInformation("Bridge bound to front end name {FrontEndName}", binding.Name);
}
+ private async Task OnActionBlock(RunMethodArgs args)
+ {
+ Result