diff --git a/src/Artemis.Core/Models/BreakableModel.cs b/src/Artemis.Core/Models/BreakableModel.cs index 7276b6adb..a752bf38b 100644 --- a/src/Artemis.Core/Models/BreakableModel.cs +++ b/src/Artemis.Core/Models/BreakableModel.cs @@ -69,11 +69,16 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel /// public void ClearBrokenState(string state) { - if (state == null) throw new ArgumentNullException(nameof(state)); + if (state == null) + throw new ArgumentNullException(nameof(state)); + + // If there was no broken state to begin with, done! if (BrokenState == null) return; - - if (BrokenState != state) return; + // Only clear similar broken states + if (BrokenState != state) + return; + BrokenState = null; BrokenStateException = null; OnBrokenStateChanged(); diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 2a9d7de6d..867fe8cb3 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -115,7 +115,7 @@ internal class NodeService : INodeService } } - node.Initialize(script); + node.TryInitialize(script); return node; } diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index b5e16a50b..eb3fc4177 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -8,7 +8,7 @@ namespace Artemis.Core; /// /// Represents a kind of node inside a /// -public interface INode : INotifyPropertyChanged +public interface INode : INotifyPropertyChanged, IBreakableModel { /// /// Gets or sets the ID of the node. @@ -81,15 +81,15 @@ public interface INode : INotifyPropertyChanged event EventHandler> PinCollectionRemoved; /// - /// Called when the node was loaded from storage or newly created + /// Attempts to initialize the node. /// /// The script the node is contained in - void Initialize(INodeScript script); + void TryInitialize(INodeScript script); /// - /// Evaluates the value of the output pins of this node + /// Attempts to evaluate the value of the output pins of this node /// - void Evaluate(); + void TryEvaluate(); /// /// Resets the node causing all pins to re-evaluate the next time is called diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 5c1ccb03f..70b313fd3 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -12,7 +12,7 @@ namespace Artemis.Core; /// /// Represents a kind of node inside a /// -public abstract class Node : CorePropertyChanged, INode +public abstract class Node : BreakableModel, INode { /// public event EventHandler? Resetting; @@ -95,6 +95,9 @@ public abstract class Node : CorePropertyChanged, INode /// public IReadOnlyCollection PinCollections => new ReadOnlyCollection(_pinCollections); + /// + public override string BrokenDisplayName => Name; + #endregion #region Construtors @@ -335,12 +338,17 @@ public abstract class Node : CorePropertyChanged, INode return isRemoved; } - /// + /// + /// Called when the node was loaded from storage or newly created + /// + /// The script the node is contained in public virtual void Initialize(INodeScript script) { } - /// + /// + /// Evaluates the value of the output pins of this node + /// public abstract void Evaluate(); /// @@ -354,6 +362,18 @@ public abstract class Node : CorePropertyChanged, INode Resetting?.Invoke(this, EventArgs.Empty); } + /// + public void TryInitialize(INodeScript script) + { + TryOrBreak(() => Initialize(script), "Failed to initialize"); + } + + /// + public void TryEvaluate() + { + TryOrBreak(Evaluate, "Failed to evaluate"); + } + /// /// Called whenever the node must show it's custom view model, if , no custom view model is used /// diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 8644c06b0..3034187f0 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -101,7 +101,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript node.Reset(); } - ExitNode.Evaluate(); + ExitNode.TryEvaluate(); } /// diff --git a/src/Artemis.Core/VisualScripting/OutputPin.cs b/src/Artemis.Core/VisualScripting/OutputPin.cs index 2e135eb13..b9fa3332d 100644 --- a/src/Artemis.Core/VisualScripting/OutputPin.cs +++ b/src/Artemis.Core/VisualScripting/OutputPin.cs @@ -43,7 +43,7 @@ public sealed class OutputPin : Pin get { if (!IsEvaluated) - Node?.Evaluate(); + Node?.TryEvaluate(); return _value; } @@ -115,7 +115,7 @@ public sealed class OutputPin : Pin get { if (!IsEvaluated) - Node?.Evaluate(); + Node?.TryEvaluate(); return _value; } diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml b/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml index aebe108cf..40849f3d2 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml @@ -37,35 +37,38 @@ ClipToBounds="True" Background="{DynamicResource ContentDialogBackground}"> - - - - + + + + - - diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs index bb3a1670d..e203356b7 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs @@ -8,6 +8,7 @@ using Artemis.Core.Events; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor.Commands; using Avalonia; @@ -21,9 +22,9 @@ namespace Artemis.UI.Screens.VisualScripting; public class NodeViewModel : ActivatableViewModelBase { private readonly INodeEditorService _nodeEditorService; + private readonly IWindowService _windowService; private ICustomNodeViewModel? _customNodeViewModel; - private ReactiveCommand? _deleteNode; private double _dragOffsetX; private double _dragOffsetY; private ObservableAsPropertyHelper? _hasInputPins; @@ -34,14 +35,13 @@ public class NodeViewModel : ActivatableViewModelBase private double _startX; private double _startY; - public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService) + public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService) { _nodeEditorService = nodeEditorService; + _windowService = windowService; NodeScriptViewModel = nodeScriptViewModel; Node = node; - DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v)); - SourceList nodePins = new(); SourceList nodePinCollections = new(); @@ -61,7 +61,10 @@ public class NodeViewModel : ActivatableViewModelBase nodePins.Connect().Merge(nodePinCollections.Connect().TransformMany(c => c.PinViewModels)).Bind(out ReadOnlyObservableCollection pins).Subscribe(); PinViewModels = pins; - + + DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v)); + ShowBrokenState = ReactiveCommand.Create(ExecuteShowBrokenState); + this.WhenActivated(d => { _isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode) @@ -152,17 +155,14 @@ public class NodeViewModel : ActivatableViewModelBase set => RaiseAndSetIfChanged(ref _customNodeViewModel, value); } - public ReactiveCommand? DeleteNode - { - get => _deleteNode; - set => RaiseAndSetIfChanged(ref _deleteNode, value); - } - public bool IsSelected { get => _isSelected; set => RaiseAndSetIfChanged(ref _isSelected, value); } + + public ReactiveCommand ShowBrokenState { get; } + public ReactiveCommand DeleteNode { get; } public void StartDrag(Point mouseStartPosition) { @@ -195,4 +195,10 @@ public class NodeViewModel : ActivatableViewModelBase { _nodeEditorService.ExecuteCommand(NodeScriptViewModel.NodeScript, new DeleteNode(NodeScriptViewModel.NodeScript, Node)); } + + private void ExecuteShowBrokenState() + { + if (Node.BrokenState != null && Node.BrokenStateException != null) + _windowService.ShowExceptionDialog(Node.BrokenState, Node.BrokenStateException); + } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs index c3c1d1a8b..810080877 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs @@ -8,7 +8,7 @@ public class StringRegexMatchNode : Node { private string? _lastPattern; private Regex? _regex; - private bool _broken; + private Exception? _exception; public StringRegexMatchNode() : base("Regex Match", "Checks provided regex pattern matches the input.") { @@ -25,20 +25,27 @@ public class StringRegexMatchNode : Node { if (Input.Value == null || Pattern.Value == null) return; - if (_broken && _lastPattern == Pattern.Value) - return; + // If the regex was invalid output false and rethrow the exception + if (_lastPattern == Pattern.Value && _exception != null) + { + Result.Value = false; + throw _exception; + } + + // If there is no regex yet or the regex changed, recompile if (_regex == null || _lastPattern != Pattern.Value) { try { _regex = new Regex(Pattern.Value, RegexOptions.Compiled); - _broken = false; + _exception = null; } - catch (Exception) + catch (Exception e) { - _broken = true; - return; + // If there is an exception, save it to keep rethrowing until the regex is fixed + _exception = e; + throw; } finally {