From 6d8f3d5ae2d18a3cf88007707b0893beaa5e6b2f Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:22:03 +0100 Subject: [PATCH 01/17] Wip changes --- Core/Core/Core.csproj | 1 - .../Bindings/ArcGISSelectionBinding.cs | 12 +- .../DUI3ControlWebView.xaml.cs | 12 +- .../Bindings/IBasicConnectorBinding.cs | 2 +- .../Bridge/BrowserBridge.cs | 146 ++++++++++-------- .../Speckle.Connectors.DUI/Bridge/IBridge.cs | 5 + .../Bridge/TopLevelExceptionHandler.cs | 99 ++++++++++++ .../ContainerRegistration.cs | 2 +- .../ContainerRegistration.cs | 7 +- 9 files changed, 206 insertions(+), 80 deletions(-) create mode 100644 DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs diff --git a/Core/Core/Core.csproj b/Core/Core/Core.csproj index 275dcc12cc..caaa2ab93a 100644 --- a/Core/Core/Core.csproj +++ b/Core/Core/Core.csproj @@ -51,7 +51,6 @@ - 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 ddc096b629..a4d08c7e8d 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs @@ -7,22 +7,22 @@ namespace Speckle.Connectors.ArcGIS.Bindings; public class ArcGISSelectionBinding : ISelectionBinding { - public string Name { get; } = "selectionBinding"; - public IBridge Parent { get; set; } + public string Name => "selectionBinding"; + public IBridge Parent { get; } - public ArcGISSelectionBinding(IBridge parent) + public ArcGISSelectionBinding(IBridge parent, TopLevelExceptionHandler 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); + Parent.Send(SelectionBindingEvents.SET_SELECTION, selInfo); } public SelectionInfo GetSelection() 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..00cb93993f 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,22 @@ 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 TopLevelExceptionHandler _topLevelExceptionHandler; - public DUI3ControlWebView(IEnumerable> bindings) + public DUI3ControlWebView(IEnumerable> bindings, TopLevelExceptionHandler 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 +34,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..026acec6a8 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -28,6 +28,7 @@ public class BrowserBridge : IBridge private readonly JsonSerializerSettings _serializerOptions; private readonly Dictionary _resultsStore = new(); private readonly SynchronizationContext _mainThreadContext; + private readonly TopLevelExceptionHandler _topLevelExceptionHandler; private Dictionary BindingMethodCache { get; set; } = new(); private ActionBlock? _actionBlock; @@ -77,7 +78,7 @@ public BrowserBridge(JsonSerializerSettings jsonSerializerSettings, ILoggerFacto { _serializerOptions = jsonSerializerSettings; _logger = loggerFactory.CreateLogger(); - + _topLevelExceptionHandler = new(_logger); //TODO: Probably we could inject this with a Lazy somewhere // Capture the main thread's SynchronizationContext _mainThreadContext = SynchronizationContext.Current; } @@ -108,19 +109,31 @@ Action showDevToolsAction // 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 result = await _topLevelExceptionHandler + .CatchUnhandled(async () => await ExecuteMethod(args.MethodName, args.MethodArgs).ConfigureAwait(false)) + .ConfigureAwait(false); + + var resultJson = JsonConvert.SerializeObject( + result.IsSuccess ? result.Value : result.Exception, + _serializerOptions + ); + NotifyUIMethodCallResultReady(args.RequestId, resultJson); + } + /// /// Used by the Frontend bridge logic to understand which methods are available. /// @@ -140,14 +153,26 @@ public string[] GetBindingsMethodNames() /// public void RunMethod(string methodName, string requestId, string args) { - _actionBlock?.Post( - new RunMethodArgs + _topLevelExceptionHandler.CatchUnhandled(Post); + return; + + void Post() + { + bool wasAccepted = _actionBlock + .NotNull() + .Post( + new RunMethodArgs + { + MethodName = methodName, + RequestId = requestId, + MethodArgs = args + } + ); + if (!wasAccepted) { - MethodName = methodName, - RequestId = requestId, - MethodArgs = args + throw new InvalidOperationException($"Action block declined to Post ({methodName} {requestId} {args})"); } - ); + } } /// @@ -170,77 +195,62 @@ public void RunOnMainThread(Action action) /// Used by the action block to invoke the actual method called by the UI. /// /// - /// /// /// - private void ExecuteMethod(string methodName, string requestId, string args) + /// The Json + private async Task ExecuteMethod(string methodName, string args) { - // Note: You might be tempted to make this method async Task to prevent the task.Wait() below. - // Do not do that! Cef65 doesn't like waiting for async .NET methods. // Note: we have this pokemon catch 'em all here because throwing errors in .NET is // very risky, and we might crash the host application. Behaviour seems also to differ // between various browser controls (e.g.: cefsharp handles things nicely - basically // passing back the exception to the browser, but webview throws an access violation // error that kills Rhino.). - try - { - if (!BindingMethodCache.TryGetValue(methodName, out MethodInfo method)) - { - throw new SpeckleException( - $"Cannot find method {methodName} in bindings class {_bindingType?.AssemblyQualifiedName}." - ); - } - - var parameters = method.GetParameters(); - var jsonArgsArray = JsonConvert.DeserializeObject(args); - if (parameters.Length != jsonArgsArray?.Length) - { - throw new SpeckleException( - $"Wrong number of arguments when invoking binding function {methodName}, expected {parameters.Length}, but got {jsonArgsArray?.Length}." - ); - } - - var typedArgs = new object[jsonArgsArray.Length]; - for (int i = 0; i < typedArgs.Length; i++) - { - var ccc = JsonConvert.DeserializeObject(jsonArgsArray[i], parameters[i].ParameterType, _serializerOptions); - if (ccc is null) - { - continue; - } - - typedArgs[i] = ccc; - } + if (!BindingMethodCache.TryGetValue(methodName, out MethodInfo method)) + { + throw new SpeckleException( + $"Cannot find method {methodName} in bindings class {_bindingType?.AssemblyQualifiedName}." + ); + } - var resultTyped = method.Invoke(Binding, typedArgs); + var parameters = method.GetParameters(); + var jsonArgsArray = JsonConvert.DeserializeObject(args); + if (parameters.Length != jsonArgsArray?.Length) + { + throw new SpeckleException( + $"Wrong number of arguments when invoking binding function {methodName}, expected {parameters.Length}, but got {jsonArgsArray?.Length}." + ); + } - string resultJson; + var typedArgs = new object[jsonArgsArray.Length]; - // Was the method called async? - if (resultTyped is not Task resultTypedTask) - { - // Regular method: no need to await things - resultJson = JsonConvert.SerializeObject(resultTyped, _serializerOptions); - } - else // It's an async call + for (int i = 0; i < typedArgs.Length; i++) + { + var ccc = JsonConvert.DeserializeObject(jsonArgsArray[i], parameters[i].ParameterType, _serializerOptions); + if (ccc is null) { - // See note at start of function. Do not asyncify! - resultTypedTask.GetAwaiter().GetResult(); - - // If has a "Result" property return the value otherwise null (Task etc) - PropertyInfo resultProperty = resultTypedTask.GetType().GetProperty("Result"); - object? taskResult = resultProperty?.GetValue(resultTypedTask); - resultJson = JsonConvert.SerializeObject(taskResult, _serializerOptions); + continue; } - NotifyUIMethodCallResultReady(requestId, resultJson); + typedArgs[i] = ccc; } - // TOP-LEVEL: Where we report unhandled exceptions as global toast notification in UI! - catch (Exception e) when (!e.IsFatal()) + + var resultTyped = method.Invoke(Binding, typedArgs); + // Was the method called async? + if (resultTyped is not Task resultTypedTask) { - ReportUnhandledError(requestId, e); + // Regular method: no need to await things + return resultTyped; } + + // It's an async call + // See note at start of function. Do not asyncify! + await resultTypedTask.ConfigureAwait(false); + + // If has a "Result" property return the value otherwise null (Task etc) + PropertyInfo? resultProperty = resultTypedTask.GetType().GetProperty(nameof(Task.Result)); + object? taskResult = resultProperty?.GetValue(resultTypedTask); + return taskResult; } /// @@ -306,6 +316,11 @@ public void OpenUrl(string url) public void Send(string eventName) { + if (_binding is null) + { + throw new InvalidOperationException("Bridge was not Initialized"); + } + var script = $"{FrontendBoundName}.emit('{eventName}')"; _scriptMethod.NotNull().Invoke(script); @@ -314,6 +329,11 @@ public void Send(string eventName) public void Send(string eventName, T data) where T : class { + if (_binding is null) + { + throw new InvalidOperationException("Bridge was not associated with a binding"); + } + string payload = JsonConvert.SerializeObject(data, _serializerOptions); string requestId = $"{Guid.NewGuid()}_{eventName}"; _resultsStore[requestId] = payload; diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs index 1bdcd6e364..3b26e58d82 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs @@ -36,8 +36,13 @@ public interface IBridge /// Action to run on main thread. public void RunOnMainThread(Action action); + /// + /// Bridge was not associated with a binding public void Send(string eventName); + /// + /// data to store + /// public void Send(string eventName, T data) where T : class; } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs new file mode 100644 index 0000000000..8d2376c0b6 --- /dev/null +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -0,0 +1,99 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Logging; +using Speckle.Connectors.DUI.Bindings; +using Speckle.Connectors.Utils; +using Speckle.Core.Logging; +using Speckle.Core.Models.Extensions; + +namespace Speckle.Connectors.DUI.Bridge; + +//Don't add to this struct, it is perfect. +public readonly struct Result +{ + public T? Value { get; } + public Exception? Exception { get; } + + [MemberNotNullWhen(false, nameof(Exception))] + public bool IsSuccess => Exception is null; + + public Result(T result) + { + Value = result; + } + + public Result([NotNull] Exception? exception) + { + Exception = exception.NotNull(); + } +} + +public class TopLevelExceptionHandler +{ + private readonly ILogger _logger; + private readonly IBridge? _bridge; + private const string UNHANDLED_LOGGER_TEMPLATE = "An unhandled Exception occured"; + + public TopLevelExceptionHandler(ILogger logger, IBridge? bridge = null) + { + _logger = logger; + _bridge = bridge; + } + + public void CatchUnhandled(Action action) + { + CatchUnhandled(() => + { + action.Invoke(); + return (object?)null; + }); + } + + public Result CatchUnhandled(Func action) + { + return CatchUnhandled(() => Task.FromResult(action.Invoke())).Result; + } + + /// + /// Invokes within a block, providing exception handling for unexpected exceptions + /// + /// + /// The to invoke + /// + /// Result pattern for the resulting action + [SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = "Top Level Exception Handler" + )] + public async Task> CatchUnhandled(Func> function) + { + try + { + return new(await function.Invoke().ConfigureAwait(false)); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + //TODO: Figure out of this is the best way to display an exception, chat with Oguzhan, perhaps we need some changes on the UI side + SetGlobalNotification(ToastNotificationType.DANGER, "Unhandled Exception Occured", ex.ToFormattedString(), false); + return new(ex); + } + catch (Exception ex) + { + _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + throw; + } + } + + private void SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose = true) => + _bridge?.Send( + BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: Hack to reference this const + new + { + type, + title, + description = message, + autoClose + } + ); +} diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs index 6671c44837..f5407e8c83 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs @@ -18,7 +18,7 @@ public static void AddDUI(this SpeckleContainerBuilder speckleContainerBuilder) speckleContainerBuilder.AddTransient(); speckleContainerBuilder.AddSingleton(); speckleContainerBuilder.AddTransient(); // POC: Each binding should have it's own bridge instance - + speckleContainerBuilder.AddSingleton(); speckleContainerBuilder.AddSingleton(GetJsonSerializerSettings()); } diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs index b444f9ba95..a1ee90f127 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs @@ -3,6 +3,7 @@ using Speckle.Autofac.DependencyInjection; using Speckle.Connectors.Utils.Cancellation; using Speckle.Connectors.Utils.Operations; +using Speckle.Core.Logging; namespace Speckle.Connectors.Utils; @@ -15,10 +16,8 @@ public static void AddConnectorUtils(this SpeckleContainerBuilder builder) builder.AddScoped(); // POC: will likely need refactoring with our reporting pattern. - var serilogLogger = new LoggerConfiguration().MinimumLevel - .Debug() - .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day) - .CreateLogger(); + //TODO: Logger will likly be removed from Core, we'll plan to figure out the config later... + var serilogLogger = SpeckleLog.Logger; ILoggerFactory loggerFactory = new LoggerFactory().AddSerilog(serilogLogger); builder.AddSingleton(loggerFactory); From 8d4e4c8c03a0437e3c980143f40acab3b2348ba3 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:51:03 +0100 Subject: [PATCH 02/17] Added top level callback in several places --- .../Bindings/AutocadSelectionBinding.cs | 24 +++--- .../DUI3ControlWebView.xaml.cs | 1 + .../Bridge/BrowserBridge.cs | 4 +- .../Bridge/TopLevelExceptionHandler.cs | 75 +++++++++++++------ .../NotNullExtensions.cs | 8 ++ 5 files changed, 76 insertions(+), 36 deletions(-) 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 b52fcba337..467dc2007d 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs @@ -8,27 +8,29 @@ namespace Speckle.Connectors.Autocad.Bindings; public class AutocadSelectionBinding : ISelectionBinding { private const string SELECTION_EVENT = "setSelection"; + private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly HashSet _visitedDocuments = new(); - private readonly List _visitedDocuments = new(); - - public string Name { get; set; } = "selectionBinding"; + public string Name { get; } = "selectionBinding"; public IBridge Parent { get; } - public AutocadSelectionBinding(IBridge parent) + public AutocadSelectionBinding(IBridge parent, TopLevelExceptionHandler 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); + private void OnDocumentChanged(Document? document) => TryRegisterDocumentForSelection(document); - private void TryRegisterDocumentForSelection(Document document) + private void TryRegisterDocumentForSelection(Document? document) { if (document == null) { @@ -38,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); } @@ -49,13 +49,13 @@ private void TryRegisterDocumentForSelection(Document document) private void OnSelectionChanged() { SelectionInfo selInfo = GetSelection(); - Parent?.Send(SELECTION_EVENT, selInfo); + Parent.Send(SELECTION_EVENT, selInfo); } public SelectionInfo GetSelection() { // POC: Will be addressed to move it into AutocadContext! https://spockle.atlassian.net/browse/CNX-9319 - Document doc = Application.DocumentManager.MdiActiveDocument; + Document? doc = Application.DocumentManager.MdiActiveDocument; List objs = new(); List objectTypes = new(); if (doc != null) 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 00cb93993f..8b831951ab 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs @@ -16,6 +16,7 @@ public DUI3ControlWebView(IEnumerable> bindings, TopLevelExceptio _bindings = bindings; _topLevelExceptionHandler = topLevelExceptionHandler; InitializeComponent(); + Browser.CoreWebView2InitializationCompleted += (sender, args) => _topLevelExceptionHandler.CatchUnhandled(() => OnInitialized(sender, args)); } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index 026acec6a8..9d63bb62d8 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -37,7 +37,7 @@ public class BrowserBridge : IBridge 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. @@ -78,7 +78,7 @@ public BrowserBridge(JsonSerializerSettings jsonSerializerSettings, ILoggerFacto { _serializerOptions = jsonSerializerSettings; _logger = loggerFactory.CreateLogger(); - _topLevelExceptionHandler = new(_logger); //TODO: Probably we could inject this with a Lazy somewhere + _topLevelExceptionHandler = new(loggerFactory, this); //TODO: Probably we could inject this with a Lazy somewhere // Capture the main thread's SynchronizationContext _mainThreadContext = SynchronizationContext.Current; } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index 8d2376c0b6..28a462f483 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -7,59 +7,89 @@ namespace Speckle.Connectors.DUI.Bridge; -//Don't add to this struct, it is perfect. +/// +/// Result Pattern struct +/// +/// public readonly struct Result { + //Don't add new members to this struct, it is perfect. public T? Value { get; } public Exception? Exception { get; } [MemberNotNullWhen(false, nameof(Exception))] public bool IsSuccess => Exception is null; + /// + /// Create a successful result + /// + /// public Result(T result) { Value = result; } - public Result([NotNull] Exception? exception) + /// + /// Create a non-sucessful result + /// + /// + /// was null + public Result([NotNull] Exception? result) { - Exception = exception.NotNull(); + Exception = result.NotNull(); } } -public class TopLevelExceptionHandler +/// +/// The functions provided by this class are designed to be used in all "top level" scenarios (e.g. Plugin, UI, and Event callbacks) +/// To provide "last ditch effort" handling of unexpected exceptions that have not been handled. +/// 1. Log events to the injected +/// 2. Display a toast notification with exception details +///
+///
+/// +/// exceptions cannot be recovered from. +/// They will be rethrown to allow the host app to run its handlers
+/// Depending on the host app, this may trigger windows event logging, and recovery snapshots before ultimately terminating the process
+/// Attempting to swallow them may lead to data corruption, deadlocking, or things worse than a managed host app crash. +///
+public sealed class TopLevelExceptionHandler { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IBridge? _bridge; private const string UNHANDLED_LOGGER_TEMPLATE = "An unhandled Exception occured"; - public TopLevelExceptionHandler(ILogger logger, IBridge? bridge = null) + public TopLevelExceptionHandler(ILoggerFactory loggerFactory, IBridge? bridge = null) { - _logger = logger; + _logger = loggerFactory.CreateLogger(); _bridge = bridge; } - public void CatchUnhandled(Action action) + /// + /// Invokes the given function within a / block, + /// and provides exception handling for unexpected exceptions that have not been handled.
+ ///
+ /// The function to invoke and provide error handling for + /// will be rethrown, these should be allowed to bubble up to the host app + /// + public void CatchUnhandled(Action function) { CatchUnhandled(() => { - action.Invoke(); + function.Invoke(); return (object?)null; }); } - public Result CatchUnhandled(Func action) + /// + /// return type + /// A result pattern struct (where exceptions have been handled) + public Result CatchUnhandled(Func function) { - return CatchUnhandled(() => Task.FromResult(action.Invoke())).Result; + return CatchUnhandled(() => Task.FromResult(function.Invoke())).Result; } - /// - /// Invokes within a block, providing exception handling for unexpected exceptions - /// - /// - /// The to invoke - /// - /// Result pattern for the resulting action + /// [SuppressMessage( "Design", "CA1031:Do not catch general exception types", @@ -74,20 +104,21 @@ public async Task> CatchUnhandled(Func> function) catch (Exception ex) when (!ex.IsFatal()) { _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); - //TODO: Figure out of this is the best way to display an exception, chat with Oguzhan, perhaps we need some changes on the UI side + + //TODO: On the UI side, we'll need to display exception messages using the UI Oguzhan made (with stack trace etc..) SetGlobalNotification(ToastNotificationType.DANGER, "Unhandled Exception Occured", ex.ToFormattedString(), false); return new(ex); } catch (Exception ex) { - _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + _logger.LogCritical(ex, UNHANDLED_LOGGER_TEMPLATE); throw; } } - private void SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose = true) => + private void SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose) => _bridge?.Send( - BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: Hack to reference this const + BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: We could move these constants into a DUI3 constants static class new { type, diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs index a34d6dd671..59d9f2b3c0 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs @@ -5,6 +5,7 @@ namespace Speckle.Connectors.Utils; public static class NotNullExtensions { + /// public static async Task NotNull( this Task task, [CallerArgumentExpression(nameof(task))] string? message = null @@ -19,6 +20,7 @@ public static async Task NotNull( return x; } + /// public static async Task NotNull( this Task task, [CallerArgumentExpression(nameof(task))] string? message = null @@ -33,6 +35,11 @@ public static async Task NotNull( return x.Value; } + /// the object to check for null + /// see + /// type + /// A non null value + /// was null public static T NotNull([NotNull] this T? obj, [CallerArgumentExpression(nameof(obj))] string? paramName = null) where T : class { @@ -43,6 +50,7 @@ public static T NotNull([NotNull] this T? obj, [CallerArgumentExpression(name return obj; } + /// public static T NotNull([NotNull] this T? obj, [CallerArgumentExpression(nameof(obj))] string? paramName = null) where T : struct { From 84ce887704246de841ccbd49bc18e0c604822953 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:01:29 +0100 Subject: [PATCH 03/17] extracting some non-controversial changes to be merged first for cleaner diff --- Core/Core/Core.csproj | 1 - .../Bindings/ArcGISSelectionBinding.cs | 6 +++--- .../Bindings/AutocadSelectionBinding.cs | 11 +++++------ .../packages.lock.json | 9 --------- DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs | 5 +++++ .../Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs | 8 ++++++++ 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Core/Core/Core.csproj b/Core/Core/Core.csproj index 275dcc12cc..caaa2ab93a 100644 --- a/Core/Core/Core.csproj +++ b/Core/Core/Core.csproj @@ -51,7 +51,6 @@ - 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 ddc096b629..157bad2797 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSelectionBinding.cs @@ -7,8 +7,8 @@ namespace Speckle.Connectors.ArcGIS.Bindings; public class ArcGISSelectionBinding : ISelectionBinding { - public string Name { get; } = "selectionBinding"; - public IBridge Parent { get; set; } + public string Name => "selectionBinding"; + public IBridge Parent { get; } public ArcGISSelectionBinding(IBridge parent) { @@ -22,7 +22,7 @@ public ArcGISSelectionBinding(IBridge parent) private void OnSelectionChanged(MapViewEventArgs args) { SelectionInfo selInfo = GetSelection(); - Parent?.Send(SelectionBindingEvents.SET_SELECTION, selInfo); + Parent.Send(SelectionBindingEvents.SET_SELECTION, selInfo); } public SelectionInfo GetSelection() 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 b52fcba337..760256fe55 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs @@ -9,9 +9,8 @@ public class AutocadSelectionBinding : ISelectionBinding { private const string SELECTION_EVENT = "setSelection"; - private readonly List _visitedDocuments = new(); + public string Name { get; } = "selectionBinding"; - public string Name { get; set; } = "selectionBinding"; public IBridge Parent { get; } @@ -26,9 +25,9 @@ public AutocadSelectionBinding(IBridge parent) Application.DocumentManager.DocumentActivated += (sender, e) => OnDocumentChanged(e.Document); } - private void OnDocumentChanged(Document document) => TryRegisterDocumentForSelection(document); + private void OnDocumentChanged(Document? document) => TryRegisterDocumentForSelection(document); - private void TryRegisterDocumentForSelection(Document document) + private void TryRegisterDocumentForSelection(Document? document) { if (document == null) { @@ -49,13 +48,13 @@ private void TryRegisterDocumentForSelection(Document document) private void OnSelectionChanged() { SelectionInfo selInfo = GetSelection(); - Parent?.Send(SELECTION_EVENT, selInfo); + Parent.Send(SELECTION_EVENT, selInfo); } public SelectionInfo GetSelection() { // POC: Will be addressed to move it into AutocadContext! https://spockle.atlassian.net/browse/CNX-9319 - Document doc = Application.DocumentManager.MdiActiveDocument; + Document? doc = Application.DocumentManager.MdiActiveDocument; List objs = new(); List objectTypes = new(); if (doc != null) diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.Tests/packages.lock.json b/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.Tests/packages.lock.json index 6fdd69eb66..8fd5af7998 100644 --- a/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.Tests/packages.lock.json +++ b/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.Tests/packages.lock.json @@ -251,14 +251,6 @@ "Serilog": "2.4.0" } }, - "Serilog.Enrichers.GlobalLogContext": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "IIZcj5mAUVhIl/NTA+YI2KC+sPDzcwvs0ZMHH42jsPfl1a4LVX7ohVpw5UK+e3GxuV3Nv239Il5oM2peUIl44g==", - "dependencies": { - "Serilog": "2.12.0" - } - }, "Serilog.Exceptions": { "type": "Transitive", "resolved": "8.4.0", @@ -529,7 +521,6 @@ "Sentry.Serilog": "[3.33.0, )", "Serilog": "[2.12.0, )", "Serilog.Enrichers.ClientInfo": "[1.3.0, )", - "Serilog.Enrichers.GlobalLogContext": "[3.0.0, )", "Serilog.Exceptions": "[8.4.0, )", "Serilog.Sinks.Console": "[4.1.0, )", "Serilog.Sinks.Seq": "[5.2.2, )", diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs index 1bdcd6e364..3b26e58d82 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs @@ -36,8 +36,13 @@ public interface IBridge /// Action to run on main thread. public void RunOnMainThread(Action action); + /// + /// Bridge was not associated with a binding public void Send(string eventName); + /// + /// data to store + /// public void Send(string eventName, T data) where T : class; } diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs index a34d6dd671..59d9f2b3c0 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/NotNullExtensions.cs @@ -5,6 +5,7 @@ namespace Speckle.Connectors.Utils; public static class NotNullExtensions { + /// public static async Task NotNull( this Task task, [CallerArgumentExpression(nameof(task))] string? message = null @@ -19,6 +20,7 @@ public static async Task NotNull( return x; } + /// public static async Task NotNull( this Task task, [CallerArgumentExpression(nameof(task))] string? message = null @@ -33,6 +35,11 @@ public static async Task NotNull( return x.Value; } + /// the object to check for null + /// see + /// type + /// A non null value + /// was null public static T NotNull([NotNull] this T? obj, [CallerArgumentExpression(nameof(obj))] string? paramName = null) where T : class { @@ -43,6 +50,7 @@ public static T NotNull([NotNull] this T? obj, [CallerArgumentExpression(name return obj; } + /// public static T NotNull([NotNull] this T? obj, [CallerArgumentExpression(nameof(obj))] string? paramName = null) where T : struct { From 09c6101f7ddd07643f41bd4eb7c439fd2ed1d646 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:48:32 +0100 Subject: [PATCH 04/17] more --- .../Bindings/AutocadSelectionBinding.cs | 2 +- .../Bindings/RhinoSelectionBinding.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 467dc2007d..3110b94c7e 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs @@ -11,7 +11,7 @@ public class AutocadSelectionBinding : ISelectionBinding private readonly TopLevelExceptionHandler _topLevelExceptionHandler; private readonly HashSet _visitedDocuments = new(); - public string Name { get; } = "selectionBinding"; + public string Name => "selectionBinding"; public IBridge Parent { get; } 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..1f24da993e 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs @@ -10,8 +10,8 @@ public class RhinoSelectionBinding : ISelectionBinding { 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) { @@ -34,7 +34,7 @@ public RhinoSelectionBinding(RhinoIdleManager idleManager, IBridge parent) private void OnSelectionChanged() { SelectionInfo selInfo = GetSelection(); - Parent?.Send(SELECTION_EVENT, selInfo); + Parent.Send(SELECTION_EVENT, selInfo); } public SelectionInfo GetSelection() From 552bf522f4a7b170cdcb6882c821aa297ec83bac Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:53:59 +0100 Subject: [PATCH 05/17] more binding stuff --- .../Bindings/AutocadSendBinding.cs | 2 +- .../Bindings/RevitBaseBinding.cs | 6 +++--- .../Bindings/RhinoBasicConnectorBinding.cs | 4 ++-- .../Bindings/RhinoReceiveBinding.cs | 4 ++-- .../Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs | 4 ++-- .../DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs | 4 ++-- DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) 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 296dd64359..8ebb187279 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs @@ -18,7 +18,7 @@ namespace Speckle.Connectors.Autocad.Bindings; public sealed class AutocadSendBinding : ISendBinding { - public string Name { get; } = "sendBinding"; + public string Name => "sendBinding"; public SendBindingUICommands Commands { get; } public IBridge Parent { get; } diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs index 7e1e0afdec..566cc9c41a 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs @@ -8,13 +8,13 @@ namespace Speckle.Connectors.Revit.Bindings; internal abstract class RevitBaseBinding : IBinding { // POC: name and bridge might be better for them to be protected props? - public string Name { get; protected set; } - public IBridge Parent { get; protected set; } + public string Name { get; } + public IBridge Parent { get; } protected readonly DocumentModelStore Store; protected readonly RevitContext RevitContext; - public RevitBaseBinding(string name, DocumentModelStore store, IBridge bridge, RevitContext revitContext) + protected RevitBaseBinding(string name, DocumentModelStore store, IBridge bridge, RevitContext revitContext) { Name = name; Parent = bridge; diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs index 4154665c7b..1ceb31b69a 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs @@ -14,8 +14,8 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoBasicConnectorBinding : IBasicConnectorBinding { - public string Name { get; set; } = "baseBinding"; - public IBridge Parent { get; set; } + public string Name => "baseBinding"; + public IBridge Parent { get; } public BasicConnectorBindingCommands Commands { get; } private readonly DocumentModelStore _store; diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs index 0314a0f78f..951ff40f6f 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs @@ -12,8 +12,8 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoReceiveBinding : IReceiveBinding { - public string Name { get; set; } = "receiveBinding"; - public IBridge Parent { get; set; } + public string Name => "receiveBinding"; + public IBridge Parent { get; } private readonly CancellationManager _cancellationManager; private readonly DocumentModelStore _store; 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 8f8594f193..b2c2ab1a20 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs @@ -18,9 +18,9 @@ namespace Speckle.Connectors.Rhino7.Bindings; public sealed class RhinoSendBinding : ISendBinding { - public string Name { get; } = "sendBinding"; + public string Name => "sendBinding"; public SendBindingUICommands Commands { get; } - public IBridge Parent { get; set; } + public IBridge Parent { get; } private readonly DocumentModelStore _store; private readonly RhinoIdleManager _idleManager; diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs index ffd61239c1..6357195a43 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs @@ -5,8 +5,8 @@ namespace Speckle.Connectors.DUI.Bindings; public class AccountBinding : IBinding { - public string Name { get; set; } = "accountsBinding"; - public IBridge Parent { get; private set; } + public string Name => "accountsBinding"; + public IBridge Parent { get; } public AccountBinding(IBridge bridge) { diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs index e4676053e0..f0523db541 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs @@ -9,8 +9,8 @@ namespace Speckle.Connectors.DUI.Bindings; ///
public class TestBinding : IBinding { - public string Name { get; set; } = "testBinding"; - public IBridge Parent { get; private set; } + public string Name => "testBinding"; + public IBridge Parent { get; } public TestBinding(IBridge bridge) { From cbbfb8caddcad53d09369c933f6ad877bbe5e956 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:55:25 +0100 Subject: [PATCH 06/17] More binding alighment --- .../Bindings/AutocadSendBinding.cs | 2 +- .../Bindings/RevitBaseBinding.cs | 6 +++--- .../Bindings/RhinoBasicConnectorBinding.cs | 4 ++-- .../Bindings/RhinoReceiveBinding.cs | 4 ++-- .../Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs | 4 ++-- .../DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs | 4 ++-- DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) 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 296dd64359..8ebb187279 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs @@ -18,7 +18,7 @@ namespace Speckle.Connectors.Autocad.Bindings; public sealed class AutocadSendBinding : ISendBinding { - public string Name { get; } = "sendBinding"; + public string Name => "sendBinding"; public SendBindingUICommands Commands { get; } public IBridge Parent { get; } diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs index 7e1e0afdec..566cc9c41a 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitBaseBinding.cs @@ -8,13 +8,13 @@ namespace Speckle.Connectors.Revit.Bindings; internal abstract class RevitBaseBinding : IBinding { // POC: name and bridge might be better for them to be protected props? - public string Name { get; protected set; } - public IBridge Parent { get; protected set; } + public string Name { get; } + public IBridge Parent { get; } protected readonly DocumentModelStore Store; protected readonly RevitContext RevitContext; - public RevitBaseBinding(string name, DocumentModelStore store, IBridge bridge, RevitContext revitContext) + protected RevitBaseBinding(string name, DocumentModelStore store, IBridge bridge, RevitContext revitContext) { Name = name; Parent = bridge; diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs index 4154665c7b..1ceb31b69a 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs @@ -14,8 +14,8 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoBasicConnectorBinding : IBasicConnectorBinding { - public string Name { get; set; } = "baseBinding"; - public IBridge Parent { get; set; } + public string Name => "baseBinding"; + public IBridge Parent { get; } public BasicConnectorBindingCommands Commands { get; } private readonly DocumentModelStore _store; diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs index 0314a0f78f..951ff40f6f 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs @@ -12,8 +12,8 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoReceiveBinding : IReceiveBinding { - public string Name { get; set; } = "receiveBinding"; - public IBridge Parent { get; set; } + public string Name => "receiveBinding"; + public IBridge Parent { get; } private readonly CancellationManager _cancellationManager; private readonly DocumentModelStore _store; 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 8f8594f193..b2c2ab1a20 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs @@ -18,9 +18,9 @@ namespace Speckle.Connectors.Rhino7.Bindings; public sealed class RhinoSendBinding : ISendBinding { - public string Name { get; } = "sendBinding"; + public string Name => "sendBinding"; public SendBindingUICommands Commands { get; } - public IBridge Parent { get; set; } + public IBridge Parent { get; } private readonly DocumentModelStore _store; private readonly RhinoIdleManager _idleManager; diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs index ffd61239c1..6357195a43 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/AccountBinding.cs @@ -5,8 +5,8 @@ namespace Speckle.Connectors.DUI.Bindings; public class AccountBinding : IBinding { - public string Name { get; set; } = "accountsBinding"; - public IBridge Parent { get; private set; } + public string Name => "accountsBinding"; + public IBridge Parent { get; } public AccountBinding(IBridge bridge) { diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs index e4676053e0..f0523db541 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/TestBinding.cs @@ -9,8 +9,8 @@ namespace Speckle.Connectors.DUI.Bindings; ///
public class TestBinding : IBinding { - public string Name { get; set; } = "testBinding"; - public IBridge Parent { get; private set; } + public string Name => "testBinding"; + public IBridge Parent { get; } public TestBinding(IBridge bridge) { From 032938a215beb9fc2d6c6dc6080c53e07d665dd7 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:56:10 +0100 Subject: [PATCH 07/17] formatting --- .../Bindings/AutocadSelectionBinding.cs | 1 - 1 file changed, 1 deletion(-) 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 760256fe55..4a5e673c9e 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs @@ -11,7 +11,6 @@ public class AutocadSelectionBinding : ISelectionBinding public string Name { get; } = "selectionBinding"; - public IBridge Parent { get; } public AutocadSelectionBinding(IBridge parent) From c3c06333505cea4608a812342447f32368c20649 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:14:30 +0100 Subject: [PATCH 08/17] fix merge mistake --- .../Bindings/AutocadSelectionBinding.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4a5e673c9e..1da7250ceb 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSelectionBinding.cs @@ -9,8 +9,8 @@ public class AutocadSelectionBinding : ISelectionBinding { private const string SELECTION_EVENT = "setSelection"; - public string Name { get; } = "selectionBinding"; - + private readonly HashSet _visitedDocuments = new(); + public string Name => "selectionBinding"; public IBridge Parent { get; } public AutocadSelectionBinding(IBridge parent) From f1e17377eb4878bef1dc0385f415645b7c489773 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:16:12 +0100 Subject: [PATCH 09/17] removed lies --- DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs index 3b26e58d82..1bdcd6e364 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/IBridge.cs @@ -36,13 +36,8 @@ public interface IBridge /// Action to run on main thread. public void RunOnMainThread(Action action); - /// - /// Bridge was not associated with a binding public void Send(string eventName); - /// - /// data to store - /// public void Send(string eventName, T data) where T : class; } From 64959f802c213e9d4e9a9d78ff636e77fa0ff820 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:15:48 +0100 Subject: [PATCH 10/17] browser bridge concurrency --- .../Bridge/BrowserBridge.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index 9d63bb62d8..5d0c0b0038 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Reflection; using System.Runtime.InteropServices; using Speckle.Newtonsoft.Json; @@ -26,11 +27,12 @@ public class BrowserBridge : IBridge /// private readonly JsonSerializerSettings _serializerOptions; - private readonly Dictionary _resultsStore = new(); + private readonly ConcurrentDictionary _resultsStore = new(); private readonly SynchronizationContext _mainThreadContext; private readonly TopLevelExceptionHandler _topLevelExceptionHandler; - private Dictionary BindingMethodCache { get; set; } = new(); + private IReadOnlyDictionary _bindingMethodCache = new Dictionary(); + private ActionBlock? _actionBlock; private Action? _scriptMethod; @@ -97,15 +99,16 @@ 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). @@ -125,7 +128,7 @@ private async Task OnActionBlock(RunMethodArgs args) { Result result = await _topLevelExceptionHandler .CatchUnhandled(async () => await ExecuteMethod(args.MethodName, args.MethodArgs).ConfigureAwait(false)) - .ConfigureAwait(false); + .ConfigureAwait(true); var resultJson = JsonConvert.SerializeObject( result.IsSuccess ? result.Value : result.Exception, @@ -140,7 +143,7 @@ private async Task OnActionBlock(RunMethodArgs args) /// public string[] GetBindingsMethodNames() { - var bindingNames = BindingMethodCache.Keys.ToArray(); + var bindingNames = _bindingMethodCache.Keys.ToArray(); Debug.WriteLine($"{FrontendBoundName}: " + JsonConvert.SerializeObject(bindingNames, Formatting.Indented)); return bindingNames; } @@ -206,7 +209,7 @@ public void RunOnMainThread(Action action) // passing back the exception to the browser, but webview throws an access violation // error that kills Rhino.). - if (!BindingMethodCache.TryGetValue(methodName, out MethodInfo method)) + if (!_bindingMethodCache.TryGetValue(methodName, out MethodInfo method)) { throw new SpeckleException( $"Cannot find method {methodName} in bindings class {_bindingType?.AssemblyQualifiedName}." @@ -244,7 +247,6 @@ public void RunOnMainThread(Action action) } // It's an async call - // See note at start of function. Do not asyncify! await resultTypedTask.ConfigureAwait(false); // If has a "Result" property return the value otherwise null (Task etc) @@ -294,8 +296,11 @@ private void NotifyUIMethodCallResultReady(string requestId, string? serializedD /// public string? GetCallResult(string requestId) { - var res = _resultsStore[requestId]; - _resultsStore.Remove(requestId); + bool isFound = _resultsStore.TryRemove(requestId, out string? res); + if (!isFound) + { + throw new ArgumentException($"No result for the given request id was found: {requestId}", nameof(requestId)); + } return res; } From d91528e779f1b351dd0ec82e68046be801313de5 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:43:18 +0100 Subject: [PATCH 11/17] More subscriptions --- .../Utils/ArcGisDocumentStore.cs | 29 +++++++++++-------- .../HostApp/AutocadDocumentModelStore.cs | 11 ++++--- .../Bindings/RhinoSelectionBinding.cs | 28 +++++++++--------- .../Bridge/TopLevelExceptionHandler.cs | 11 +++++++ 4 files changed, 50 insertions(+), 29 deletions(-) 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..c536a1bf05 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,38 @@ namespace Speckle.Connectors.ArcGIS.Utils; public class ArcGISDocumentStore : DocumentModelStore { - public ArcGISDocumentStore(JsonSerializerSettings serializerOption) + public ArcGISDocumentStore(JsonSerializerSettings serializerOption, TopLevelExceptionHandler topLevelExceptionHandler) : base(serializerOption, true) { ActiveMapViewChangedEvent.Subscribe(OnMapViewChanged); - ProjectSavingEvent.Subscribe(OnProjectSaving); - ProjectClosingEvent.Subscribe(OnProjectClosing); + 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/HostApp/AutocadDocumentModelStore.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs index 17ebfccee0..57b136c964 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, + TopLevelExceptionHandler 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/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs index 1f24da993e..893b3ef515 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs @@ -8,30 +8,32 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoSelectionBinding : ISelectionBinding { + private readonly TopLevelExceptionHandler _topLevelExceptionHandler; private const string SELECTION_EVENT = "setSelection"; public string Name => "selectionBinding"; public IBridge Parent { get; } - public RhinoSelectionBinding(RhinoIdleManager idleManager, IBridge parent) + public RhinoSelectionBinding( + RhinoIdleManager idleManager, + IBridge parent, + TopLevelExceptionHandler topLevelExceptionHandler + ) { + _topLevelExceptionHandler = topLevelExceptionHandler; Parent = parent; - RhinoDoc.SelectObjects += (_, _) => - { - idleManager.SubscribeToIdle(OnSelectionChanged); - }; - RhinoDoc.DeselectObjects += (_, _) => - { - idleManager.SubscribeToIdle(OnSelectionChanged); - }; - RhinoDoc.DeselectAllObjects += (_, _) => + RhinoDoc.SelectObjects += OnSelectionChange; + RhinoDoc.DeselectObjects += OnSelectionChange; + RhinoDoc.DeselectAllObjects += OnSelectionChange; + + void OnSelectionChange(object o, EventArgs eventArgs) { - idleManager.SubscribeToIdle(OnSelectionChanged); - }; + idleManager.SubscribeToIdle(() => _topLevelExceptionHandler.CatchUnhandled(UpdateSelection)); + } } - private void OnSelectionChanged() + private void UpdateSelection() { SelectionInfo selInfo = GetSelection(); Parent.Send(SELECTION_EVENT, selInfo); diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index 28a462f483..4de920ca49 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -89,6 +89,17 @@ public Result CatchUnhandled(Func function) return CatchUnhandled(() => Task.FromResult(function.Invoke())).Result; } + /// + public async Task CatchUnhandled(Func function) + { + await CatchUnhandled(async () => + { + await function.Invoke().ConfigureAwait(false); + return null; + }) + .ConfigureAwait(false); + } + /// [SuppressMessage( "Design", From fad5d5f77239238c40b431b80d2d327c3d942935 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:24:05 +0100 Subject: [PATCH 12/17] Small tweaks --- .../Bridge/TopLevelExceptionHandler.cs | 18 ++++-------------- .../ContainerRegistration.cs | 2 +- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index 4de920ca49..aaebb99748 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -56,10 +56,11 @@ public Result([NotNull] Exception? result) public sealed class TopLevelExceptionHandler { private readonly ILogger _logger; - private readonly IBridge? _bridge; + private readonly IBridge _bridge; + private const string UNHANDLED_LOGGER_TEMPLATE = "An unhandled Exception occured"; - public TopLevelExceptionHandler(ILoggerFactory loggerFactory, IBridge? bridge = null) + public TopLevelExceptionHandler(ILoggerFactory loggerFactory, IBridge bridge) { _logger = loggerFactory.CreateLogger(); _bridge = bridge; @@ -89,17 +90,6 @@ public Result CatchUnhandled(Func function) return CatchUnhandled(() => Task.FromResult(function.Invoke())).Result; } - /// - public async Task CatchUnhandled(Func function) - { - await CatchUnhandled(async () => - { - await function.Invoke().ConfigureAwait(false); - return null; - }) - .ConfigureAwait(false); - } - /// [SuppressMessage( "Design", @@ -128,7 +118,7 @@ public async Task> CatchUnhandled(Func> function) } private void SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose) => - _bridge?.Send( + _bridge.Send( BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: We could move these constants into a DUI3 constants static class new { diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs index a1ee90f127..4c44759881 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs @@ -16,7 +16,7 @@ public static void AddConnectorUtils(this SpeckleContainerBuilder builder) builder.AddScoped(); // POC: will likely need refactoring with our reporting pattern. - //TODO: Logger will likly be removed from Core, we'll plan to figure out the config later... + //TODO: Logger will likely be removed from Core, we'll plan to figure out the config later... var serilogLogger = SpeckleLog.Logger; ILoggerFactory loggerFactory = new LoggerFactory().AddSerilog(serilogLogger); From 18c5ed47e7a1f81ab9ef5407001e2f723a5363fa Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:14:53 +0100 Subject: [PATCH 13/17] Resolved some PR comments --- .../Bridge/BrowserBridge.cs | 45 +++++++++---------- DUI3-DX/Directory.Build.props | 7 +++ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index 5d0c0b0038..c1bbe114da 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -2,7 +2,6 @@ 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; @@ -19,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. @@ -130,10 +129,7 @@ private async Task OnActionBlock(RunMethodArgs args) .CatchUnhandled(async () => await ExecuteMethod(args.MethodName, args.MethodArgs).ConfigureAwait(false)) .ConfigureAwait(true); - var resultJson = JsonConvert.SerializeObject( - result.IsSuccess ? result.Value : result.Exception, - _serializerOptions - ); + string? resultJson = result.IsSuccess ? JsonConvert.SerializeObject(result.Value, _serializerOptions) : null; NotifyUIMethodCallResultReady(args.RequestId, resultJson); } @@ -199,20 +195,16 @@ public void RunOnMainThread(Action action) /// /// /// - /// + /// The was not found or the given were not valid for the method call + /// The invoked method throws an exception /// The Json private async Task ExecuteMethod(string methodName, string args) { - // Note: we have this pokemon catch 'em all here because throwing errors in .NET is - // very risky, and we might crash the host application. Behaviour seems also to differ - // between various browser controls (e.g.: cefsharp handles things nicely - basically - // passing back the exception to the browser, but webview throws an access violation - // error that kills Rhino.). - if (!_bindingMethodCache.TryGetValue(methodName, out MethodInfo method)) { - throw new SpeckleException( - $"Cannot find method {methodName} in bindings class {_bindingType?.AssemblyQualifiedName}." + throw new ArgumentException( + $"Cannot find method {methodName} in bindings class {_bindingType?.AssemblyQualifiedName}.", + nameof(methodName) ); } @@ -220,25 +212,30 @@ public void RunOnMainThread(Action action) var jsonArgsArray = JsonConvert.DeserializeObject(args); if (parameters.Length != jsonArgsArray?.Length) { - throw new SpeckleException( - $"Wrong number of arguments when invoking binding function {methodName}, expected {parameters.Length}, but got {jsonArgsArray?.Length}." + throw new ArgumentException( + $"Wrong number of arguments when invoking binding function {methodName}, expected {parameters.Length}, but got {jsonArgsArray?.Length}.", + nameof(args) ); } - var typedArgs = new object[jsonArgsArray.Length]; + var typedArgs = new object?[jsonArgsArray.Length]; for (int i = 0; i < typedArgs.Length; i++) { var ccc = JsonConvert.DeserializeObject(jsonArgsArray[i], parameters[i].ParameterType, _serializerOptions); - if (ccc is null) - { - continue; - } - typedArgs[i] = ccc; } - var resultTyped = method.Invoke(Binding, typedArgs); + object? resultTyped; + try + { + resultTyped = method.Invoke(Binding, typedArgs); + } + catch (TargetInvocationException ex) + { + throw new TargetInvocationException($"Unhandled exception while executing {methodName}", ex.InnerException); + } + // Was the method called async? if (resultTyped is not Task resultTypedTask) { diff --git a/DUI3-DX/Directory.Build.props b/DUI3-DX/Directory.Build.props index 56a58f6ad6..71b512dc6f 100644 --- a/DUI3-DX/Directory.Build.props +++ b/DUI3-DX/Directory.Build.props @@ -7,6 +7,13 @@ true false + + + + 3.0.999-local + 3.0.999.0000 + $(FileVersion) + From fbdf66b576dadcb3f513182dcdde7c1e074acfef Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:34:07 +0100 Subject: [PATCH 14/17] last rounds of polish --- .../Bridge/BrowserBridge.cs | 22 +++++++-------- .../Bridge/TopLevelExceptionHandler.cs | 28 ++++++++++--------- DUI3-DX/Directory.Build.props | 7 ----- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index c1bbe114da..b0df1e1991 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -127,9 +127,12 @@ private async Task OnActionBlock(RunMethodArgs args) { Result result = await _topLevelExceptionHandler .CatchUnhandled(async () => await ExecuteMethod(args.MethodName, args.MethodArgs).ConfigureAwait(false)) - .ConfigureAwait(true); + .ConfigureAwait(false); + + string resultJson = result.IsSuccess + ? JsonConvert.SerializeObject(result.Value, _serializerOptions) + : SerializeFormattedException(result.Exception); - string? resultJson = result.IsSuccess ? JsonConvert.SerializeObject(result.Value, _serializerOptions) : null; NotifyUIMethodCallResultReady(args.RequestId, resultJson); } @@ -255,22 +258,17 @@ public void RunOnMainThread(Action action) /// /// Errors that not handled on bindings. /// - private void ReportUnhandledError(string requestId, Exception e) + private string SerializeFormattedException(Exception e) { - var message = e.Message; - if (e is TargetInvocationException tie) // Exception on SYNC function calls. Message should be passed from inner exception since it is wrapped. - { - message = tie.InnerException?.Message; - } + //TODO: I'm not sure we still require this... the top level handler is already displaying the toast var errorDetails = new { - Message = message, // Topmost message + Message = e.Message, // Topmost message Error = e.ToFormattedString(), // All messages from exceptions - StackTrace = e.ToString() + StackTrace = e.ToString(), }; - var serializedError = JsonConvert.SerializeObject(errorDetails, _serializerOptions); - NotifyUIMethodCallResultReady(requestId, serializedError); + return JsonConvert.SerializeObject(errorDetails, _serializerOptions); } /// diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index aaebb99748..827b3d7e1c 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -91,24 +91,26 @@ public Result CatchUnhandled(Func function) } /// - [SuppressMessage( - "Design", - "CA1031:Do not catch general exception types", - Justification = "Top Level Exception Handler" - )] public async Task> CatchUnhandled(Func> function) { try { - return new(await function.Invoke().ConfigureAwait(false)); - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); + try + { + return new(await function.Invoke().ConfigureAwait(false)); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); - //TODO: On the UI side, we'll need to display exception messages using the UI Oguzhan made (with stack trace etc..) - SetGlobalNotification(ToastNotificationType.DANGER, "Unhandled Exception Occured", ex.ToFormattedString(), false); - return new(ex); + SetGlobalNotification( + ToastNotificationType.DANGER, + "Unhandled Exception Occured", + ex.ToFormattedString(), + false + ); + return new(ex); + } } catch (Exception ex) { diff --git a/DUI3-DX/Directory.Build.props b/DUI3-DX/Directory.Build.props index 71b512dc6f..56a58f6ad6 100644 --- a/DUI3-DX/Directory.Build.props +++ b/DUI3-DX/Directory.Build.props @@ -7,13 +7,6 @@ true false - - - - 3.0.999-local - 3.0.999.0000 - $(FileVersion) - From cd956bd216319f41d2c8bbe7823d693dec56bc14 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:06:32 +0100 Subject: [PATCH 15/17] local to private function --- .../Bindings/RhinoSelectionBinding.cs | 10 ++++++---- .../Speckle.Connectors.Utils/ContainerRegistration.cs | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) 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 893b3ef515..a101f16f95 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs @@ -8,6 +8,7 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoSelectionBinding : ISelectionBinding { + private readonly RhinoIdleManager _idleManager; private readonly TopLevelExceptionHandler _topLevelExceptionHandler; private const string SELECTION_EVENT = "setSelection"; @@ -20,17 +21,18 @@ public RhinoSelectionBinding( TopLevelExceptionHandler topLevelExceptionHandler ) { + _idleManager = idleManager; _topLevelExceptionHandler = topLevelExceptionHandler; Parent = parent; RhinoDoc.SelectObjects += OnSelectionChange; RhinoDoc.DeselectObjects += OnSelectionChange; RhinoDoc.DeselectAllObjects += OnSelectionChange; + } - void OnSelectionChange(object o, EventArgs eventArgs) - { - idleManager.SubscribeToIdle(() => _topLevelExceptionHandler.CatchUnhandled(UpdateSelection)); - } + void OnSelectionChange(object o, EventArgs eventArgs) + { + _idleManager.SubscribeToIdle(() => _topLevelExceptionHandler.CatchUnhandled(UpdateSelection)); } private void UpdateSelection() diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs index 4c44759881..3168ac7709 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/ContainerRegistration.cs @@ -15,7 +15,6 @@ public static void AddConnectorUtils(this SpeckleContainerBuilder builder) builder.AddSingleton(); builder.AddScoped(); - // POC: will likely need refactoring with our reporting pattern. //TODO: Logger will likely be removed from Core, we'll plan to figure out the config later... var serilogLogger = SpeckleLog.Logger; From c5fe301cc6b3a85f1960ee94851e4015a62b842c Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:43:38 +0100 Subject: [PATCH 16/17] More events that are top level --- All.sln.DotSettings | 1 + .../Bindings/ArcGISSendBinding.cs | 46 ++++++++++--- .../Utils/ArcGisDocumentStore.cs | 2 +- .../Bindings/AutocadSendBinding.cs | 20 ++++-- .../Bindings/RevitSendBinding.cs | 11 +++- .../Bindings/SelectionBinding.cs | 7 +- .../HostApp/RevitDocumentStore.cs | 15 +++-- .../Plugin/RevitIdleManager.cs | 22 ++++--- .../Bindings/RhinoSendBinding.cs | 66 +++++++++---------- .../HostApp/RhinoDocumentStore.cs | 39 ++++++----- 10 files changed, 146 insertions(+), 83 deletions(-) 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/ArcGISSendBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index 3773833114..e7432b7f27 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 TopLevelExceptionHandler _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, + TopLevelExceptionHandler 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 c536a1bf05..91c8e6d881 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs @@ -15,7 +15,7 @@ public class ArcGISDocumentStore : DocumentModelStore public ArcGISDocumentStore(JsonSerializerSettings serializerOption, TopLevelExceptionHandler topLevelExceptionHandler) : base(serializerOption, true) { - ActiveMapViewChangedEvent.Subscribe(OnMapViewChanged); + ActiveMapViewChangedEvent.Subscribe(a => topLevelExceptionHandler.CatchUnhandled(() => OnMapViewChanged(a))); ProjectSavingEvent.Subscribe(_ => { topLevelExceptionHandler.CatchUnhandled(OnProjectSaving); 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 8ebb187279..876feb91c9 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 TopLevelExceptionHandler _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, + TopLevelExceptionHandler 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/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index ab60430881..338e5a264e 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 TopLevelExceptionHandler _topLevelExceptionHandler; public RevitSendBinding( IRevitIdleManager idleManager, @@ -36,7 +37,8 @@ public RevitSendBinding( IBridge bridge, IUnitOfWorkFactory unitOfWorkFactory, RevitSettings revitSettings, - ISendConversionCache sendConversionCache + ISendConversionCache sendConversionCache, + TopLevelExceptionHandler 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..8c122ca7a4 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 TopLevelExceptionHandler _topLevelExceptionHandler; public SelectionBinding( RevitContext revitContext, DocumentModelStore store, IRevitIdleManager idleManager, - IBridge bridge + IBridge bridge, + TopLevelExceptionHandler 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..0bccb6f880 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, + TopLevelExceptionHandler 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..184aa4a54a 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 TopLevelExceptionHandler _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, TopLevelExceptionHandler 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/RhinoSendBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs index b2c2ab1a20..f0563f93bb 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 TopLevelExceptionHandler _topLevelExceptionHandler; public RhinoSendBinding( DocumentModelStore store, @@ -46,7 +47,8 @@ public RhinoSendBinding( IUnitOfWorkFactory unitOfWorkFactory, RhinoSettings rhinoSettings, CancellationManager cancellationManager, - ISendConversionCache sendConversionCache + ISendConversionCache sendConversionCache, + TopLevelExceptionHandler 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..f3e144e4cf 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 TopLevelExceptionHandler _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, + TopLevelExceptionHandler 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() From 0b352a42d11f88422f164fe06d235f10947ac34e Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:50:27 +0100 Subject: [PATCH 17/17] Interfaced out TopLevelExceptionHandler --- .../Bindings/ArcGISSelectionBinding.cs | 2 +- .../Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs | 4 ++-- .../Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs | 5 ++++- .../Bindings/AutocadSelectionBinding.cs | 4 ++-- .../Bindings/AutocadSendBinding.cs | 4 ++-- .../HostApp/AutocadDocumentModelStore.cs | 2 +- .../Bindings/RevitSendBinding.cs | 4 ++-- .../Bindings/SelectionBinding.cs | 4 ++-- .../HostApp/RevitDocumentStore.cs | 2 +- .../Plugin/RevitIdleManager.cs | 4 ++-- .../Bindings/RhinoSelectionBinding.cs | 4 ++-- .../Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs | 4 ++-- .../Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs | 4 ++-- .../DUI3ControlWebView.xaml.cs | 4 ++-- DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs | 4 ++-- .../Bridge/TopLevelExceptionHandler.cs | 4 +++- DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs | 2 +- 17 files changed, 33 insertions(+), 28 deletions(-) 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 a4d08c7e8d..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,7 +10,7 @@ public class ArcGISSelectionBinding : ISelectionBinding public string Name => "selectionBinding"; public IBridge Parent { get; } - public ArcGISSelectionBinding(IBridge parent, TopLevelExceptionHandler topLevelHandler) + public ArcGISSelectionBinding(IBridge parent, ITopLevelExceptionHandler topLevelHandler) { Parent = parent; 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 e7432b7f27..3f80e26c23 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -31,7 +31,7 @@ public sealed class ArcGISSendBinding : ISendBinding private readonly List _sendFilters; private readonly CancellationManager _cancellationManager; private readonly ISendConversionCache _sendConversionCache; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; /// /// Used internally to aggregate the changed objects' id. @@ -47,7 +47,7 @@ public ArcGISSendBinding( IUnitOfWorkFactory unitOfWorkFactory, CancellationManager cancellationManager, ISendConversionCache sendConversionCache, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) { _store = store; 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 91c8e6d881..db0df3550f 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs @@ -12,7 +12,10 @@ namespace Speckle.Connectors.ArcGIS.Utils; public class ArcGISDocumentStore : DocumentModelStore { - public ArcGISDocumentStore(JsonSerializerSettings serializerOption, TopLevelExceptionHandler topLevelExceptionHandler) + public ArcGISDocumentStore( + JsonSerializerSettings serializerOption, + ITopLevelExceptionHandler topLevelExceptionHandler + ) : base(serializerOption, true) { ActiveMapViewChangedEvent.Subscribe(a => topLevelExceptionHandler.CatchUnhandled(() => OnMapViewChanged(a))); 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 3110b94c7e..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,14 +8,14 @@ namespace Speckle.Connectors.Autocad.Bindings; public class AutocadSelectionBinding : ISelectionBinding { private const string SELECTION_EVENT = "setSelection"; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; private readonly HashSet _visitedDocuments = new(); public string Name => "selectionBinding"; public IBridge Parent { get; } - public AutocadSelectionBinding(IBridge parent, TopLevelExceptionHandler topLevelExceptionHandler) + public AutocadSelectionBinding(IBridge parent, ITopLevelExceptionHandler topLevelExceptionHandler) { _topLevelExceptionHandler = topLevelExceptionHandler; Parent = parent; 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 876feb91c9..2803920035 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs @@ -29,7 +29,7 @@ public sealed class AutocadSendBinding : ISendBinding private readonly IUnitOfWorkFactory _unitOfWorkFactory; private readonly AutocadSettings _autocadSettings; private readonly ISendConversionCache _sendConversionCache; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; /// /// Used internally to aggregate the changed objects' id. @@ -45,7 +45,7 @@ public AutocadSendBinding( AutocadSettings autocadSettings, IUnitOfWorkFactory unitOfWorkFactory, ISendConversionCache sendConversionCache, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) { _store = store; 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 57b136c964..a01b4a0716 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs @@ -14,7 +14,7 @@ public class AutocadDocumentStore : DocumentModelStore public AutocadDocumentStore( JsonSerializerSettings jsonSerializerSettings, AutocadDocumentManager autocadDocumentManager, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) : base(jsonSerializerSettings, true) { 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 338e5a264e..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,7 +27,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding private readonly CancellationManager _cancellationManager; private readonly IUnitOfWorkFactory _unitOfWorkFactory; private readonly ISendConversionCache _sendConversionCache; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; public RevitSendBinding( IRevitIdleManager idleManager, @@ -38,7 +38,7 @@ public RevitSendBinding( IUnitOfWorkFactory unitOfWorkFactory, RevitSettings revitSettings, ISendConversionCache sendConversionCache, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) : base("sendBinding", store, bridge, revitContext) { 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 8c122ca7a4..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,14 +11,14 @@ namespace Speckle.Connectors.Revit.Bindings; internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding { private readonly IRevitIdleManager _revitIdleManager; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; public SelectionBinding( RevitContext revitContext, DocumentModelStore store, IRevitIdleManager idleManager, IBridge bridge, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) : base("selectionBinding", store, bridge, revitContext) { 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 0bccb6f880..ba9b7d8db3 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -31,7 +31,7 @@ public RevitDocumentStore( JsonSerializerSettings serializerSettings, DocumentModelStorageSchema documentModelStorageSchema, IdStorageSchema idStorageSchema, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) : base(serializerSettings, true) { 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 184aa4a54a..6e3d0c4e5e 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs @@ -10,7 +10,7 @@ namespace Speckle.Connectors.Revit.Plugin; // is probably misnamed, perhaps OnIdleCallbackManager internal sealed class RevitIdleManager : IRevitIdleManager { - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; private readonly UIApplication _uiApplication; private readonly ConcurrentDictionary _calls = new(); @@ -18,7 +18,7 @@ internal sealed class RevitIdleManager : IRevitIdleManager // POC: still not thread safe private volatile bool _hasSubscribed; - public RevitIdleManager(RevitContext revitContext, TopLevelExceptionHandler topLevelExceptionHandler) + public RevitIdleManager(RevitContext revitContext, ITopLevelExceptionHandler topLevelExceptionHandler) { _topLevelExceptionHandler = topLevelExceptionHandler; _uiApplication = revitContext.UIApplication!; 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 a101f16f95..ded20ed05f 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSelectionBinding.cs @@ -9,7 +9,7 @@ namespace Speckle.Connectors.Rhino7.Bindings; public class RhinoSelectionBinding : ISelectionBinding { private readonly RhinoIdleManager _idleManager; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; private const string SELECTION_EVENT = "setSelection"; public string Name => "selectionBinding"; @@ -18,7 +18,7 @@ public class RhinoSelectionBinding : ISelectionBinding public RhinoSelectionBinding( RhinoIdleManager idleManager, IBridge parent, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) { _idleManager = idleManager; 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 f0563f93bb..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,7 +36,7 @@ public sealed class RhinoSendBinding : ISendBinding private HashSet ChangedObjectIds { get; set; } = new(); private readonly ISendConversionCache _sendConversionCache; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; public RhinoSendBinding( DocumentModelStore store, @@ -48,7 +48,7 @@ public RhinoSendBinding( RhinoSettings rhinoSettings, CancellationManager cancellationManager, ISendConversionCache sendConversionCache, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) { _store = store; 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 f3e144e4cf..40ac283bce 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/HostApp/RhinoDocumentStore.cs @@ -7,13 +7,13 @@ namespace Speckle.Connectors.Rhino7.HostApp; public class RhinoDocumentStore : DocumentModelStore { - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + 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, - TopLevelExceptionHandler topLevelExceptionHandler + ITopLevelExceptionHandler topLevelExceptionHandler ) : base(jsonSerializerSettings, true) { 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 8b831951ab..c3153a0d5f 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI.WebView/DUI3ControlWebView.xaml.cs @@ -9,9 +9,9 @@ namespace Speckle.Connectors.DUI.WebView; public sealed partial class DUI3ControlWebView : UserControl { private readonly IEnumerable> _bindings; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; - public DUI3ControlWebView(IEnumerable> bindings, TopLevelExceptionHandler topLevelExceptionHandler) + public DUI3ControlWebView(IEnumerable> bindings, ITopLevelExceptionHandler topLevelExceptionHandler) { _bindings = bindings; _topLevelExceptionHandler = topLevelExceptionHandler; diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index b0df1e1991..aa26374558 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -28,7 +28,7 @@ public sealed class BrowserBridge : IBridge private readonly JsonSerializerSettings _serializerOptions; private readonly ConcurrentDictionary _resultsStore = new(); private readonly SynchronizationContext _mainThreadContext; - private readonly TopLevelExceptionHandler _topLevelExceptionHandler; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; private IReadOnlyDictionary _bindingMethodCache = new Dictionary(); @@ -79,7 +79,7 @@ public BrowserBridge(JsonSerializerSettings jsonSerializerSettings, ILoggerFacto { _serializerOptions = jsonSerializerSettings; _logger = loggerFactory.CreateLogger(); - _topLevelExceptionHandler = new(loggerFactory, this); //TODO: Probably we could inject this with a Lazy somewhere + _topLevelExceptionHandler = new TopLevelExceptionHandler(loggerFactory, this); //TODO: Probably we could inject this with a Lazy somewhere // Capture the main thread's SynchronizationContext _mainThreadContext = SynchronizationContext.Current; } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs index 827b3d7e1c..679303cfe9 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/TopLevelExceptionHandler.cs @@ -4,6 +4,7 @@ using Speckle.Connectors.Utils; using Speckle.Core.Logging; using Speckle.Core.Models.Extensions; +using Speckle.InterfaceGenerator; namespace Speckle.Connectors.DUI.Bridge; @@ -53,7 +54,8 @@ public Result([NotNull] Exception? result) /// Depending on the host app, this may trigger windows event logging, and recovery snapshots before ultimately terminating the process
/// Attempting to swallow them may lead to data corruption, deadlocking, or things worse than a managed host app crash. /// -public sealed class TopLevelExceptionHandler +[GenerateAutoInterface] +public sealed class TopLevelExceptionHandler : ITopLevelExceptionHandler { private readonly ILogger _logger; private readonly IBridge _bridge; diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs index f5407e8c83..affb3e031b 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs @@ -18,7 +18,7 @@ public static void AddDUI(this SpeckleContainerBuilder speckleContainerBuilder) speckleContainerBuilder.AddTransient(); speckleContainerBuilder.AddSingleton(); speckleContainerBuilder.AddTransient(); // POC: Each binding should have it's own bridge instance - speckleContainerBuilder.AddSingleton(); + speckleContainerBuilder.AddSingleton(); speckleContainerBuilder.AddSingleton(GetJsonSerializerSettings()); }