1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Nodes - Show errors in node editor when a node fails to evaluate

This commit is contained in:
Robert 2022-09-01 20:33:55 +02:00
parent cd537051ca
commit eca8985fc4
9 changed files with 89 additions and 48 deletions

View File

@ -69,11 +69,16 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
/// <inheritdoc /> /// <inheritdoc />
public void ClearBrokenState(string state) 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) if (BrokenState == null)
return; return;
// Only clear similar broken states
if (BrokenState != state)
return;
if (BrokenState != state) return;
BrokenState = null; BrokenState = null;
BrokenStateException = null; BrokenStateException = null;
OnBrokenStateChanged(); OnBrokenStateChanged();

View File

@ -115,7 +115,7 @@ internal class NodeService : INodeService
} }
} }
node.Initialize(script); node.TryInitialize(script);
return node; return node;
} }

View File

@ -8,7 +8,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a kind of node inside a <see cref="INodeScript" /> /// Represents a kind of node inside a <see cref="INodeScript" />
/// </summary> /// </summary>
public interface INode : INotifyPropertyChanged public interface INode : INotifyPropertyChanged, IBreakableModel
{ {
/// <summary> /// <summary>
/// Gets or sets the ID of the node. /// Gets or sets the ID of the node.
@ -81,15 +81,15 @@ public interface INode : INotifyPropertyChanged
event EventHandler<SingleValueEventArgs<IPinCollection>> PinCollectionRemoved; event EventHandler<SingleValueEventArgs<IPinCollection>> PinCollectionRemoved;
/// <summary> /// <summary>
/// Called when the node was loaded from storage or newly created /// Attempts to initialize the node.
/// </summary> /// </summary>
/// <param name="script">The script the node is contained in</param> /// <param name="script">The script the node is contained in</param>
void Initialize(INodeScript script); void TryInitialize(INodeScript script);
/// <summary> /// <summary>
/// Evaluates the value of the output pins of this node /// Attempts to evaluate the value of the output pins of this node
/// </summary> /// </summary>
void Evaluate(); void TryEvaluate();
/// <summary> /// <summary>
/// Resets the node causing all pins to re-evaluate the next time <see cref="Evaluate" /> is called /// Resets the node causing all pins to re-evaluate the next time <see cref="Evaluate" /> is called

View File

@ -12,7 +12,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a kind of node inside a <see cref="NodeScript" /> /// Represents a kind of node inside a <see cref="NodeScript" />
/// </summary> /// </summary>
public abstract class Node : CorePropertyChanged, INode public abstract class Node : BreakableModel, INode
{ {
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler? Resetting; public event EventHandler? Resetting;
@ -95,6 +95,9 @@ public abstract class Node : CorePropertyChanged, INode
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections); public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections);
/// <inheritdoc />
public override string BrokenDisplayName => Name;
#endregion #endregion
#region Construtors #region Construtors
@ -335,12 +338,17 @@ public abstract class Node : CorePropertyChanged, INode
return isRemoved; return isRemoved;
} }
/// <inheritdoc /> /// <summary>
/// Called when the node was loaded from storage or newly created
/// </summary>
/// <param name="script">The script the node is contained in</param>
public virtual void Initialize(INodeScript script) public virtual void Initialize(INodeScript script)
{ {
} }
/// <inheritdoc /> /// <summary>
/// Evaluates the value of the output pins of this node
/// </summary>
public abstract void Evaluate(); public abstract void Evaluate();
/// <inheritdoc /> /// <inheritdoc />
@ -354,6 +362,18 @@ public abstract class Node : CorePropertyChanged, INode
Resetting?.Invoke(this, EventArgs.Empty); Resetting?.Invoke(this, EventArgs.Empty);
} }
/// <inheritdoc />
public void TryInitialize(INodeScript script)
{
TryOrBreak(() => Initialize(script), "Failed to initialize");
}
/// <inheritdoc />
public void TryEvaluate()
{
TryOrBreak(Evaluate, "Failed to evaluate");
}
/// <summary> /// <summary>
/// Called whenever the node must show it's custom view model, if <see langword="null" />, no custom view model is used /// Called whenever the node must show it's custom view model, if <see langword="null" />, no custom view model is used
/// </summary> /// </summary>

View File

@ -101,7 +101,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
node.Reset(); node.Reset();
} }
ExitNode.Evaluate(); ExitNode.TryEvaluate();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -43,7 +43,7 @@ public sealed class OutputPin<T> : Pin
get get
{ {
if (!IsEvaluated) if (!IsEvaluated)
Node?.Evaluate(); Node?.TryEvaluate();
return _value; return _value;
} }
@ -115,7 +115,7 @@ public sealed class OutputPin : Pin
get get
{ {
if (!IsEvaluated) if (!IsEvaluated)
Node?.Evaluate(); Node?.TryEvaluate();
return _value; return _value;
} }

View File

@ -37,26 +37,29 @@
ClipToBounds="True" ClipToBounds="True"
Background="{DynamicResource ContentDialogBackground}"> Background="{DynamicResource ContentDialogBackground}">
<Border Background="{DynamicResource TaskDialogHeaderBackground}"> <Border Background="{DynamicResource TaskDialogHeaderBackground}">
<Grid Classes="node-header" <Grid Classes="node-header" VerticalAlignment="Top" ColumnDefinitions="Auto,*,Auto">
VerticalAlignment="Top" <Button Grid.Column="0"
ColumnDefinitions="*,Auto"> VerticalAlignment="Center"
<TextBlock VerticalAlignment="Center" IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="10 0 0 0"
Text="{CompiledBinding Node.Name}"
ToolTip.Tip="{CompiledBinding Node.Description}">
</TextBlock>
<Button VerticalAlignment="Center"
Classes="icon-button icon-button-small" Classes="icon-button icon-button-small"
Grid.Column="1" Foreground="White"
Margin="5" Background="#E74C4C"
Command="{CompiledBinding DeleteNode}"> BorderBrush="#E74C4C"
Margin="5 0 0 0"
ToolTip.Tip="{CompiledBinding Node.BrokenState}"
Command="{CompiledBinding ShowBrokenState}">
<avalonia:MaterialIcon Kind="AlertCircle"></avalonia:MaterialIcon>
</Button>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}"/>
<Button Grid.Column="2" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
</Button> </Button>
</Grid> </Grid>
</Border> </Border>
</Border> </Border>
<Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="4"> <Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="4">
<StackPanel Grid.Column="0" IsVisible="{CompiledBinding HasInputPins}"> <StackPanel Grid.Column="0" IsVisible="{CompiledBinding HasInputPins}">
<ItemsControl Items="{CompiledBinding InputPinViewModels}" Margin="4 0" /> <ItemsControl Items="{CompiledBinding InputPinViewModels}" Margin="4 0" />

View File

@ -8,6 +8,7 @@ using Artemis.Core.Events;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands; using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia; using Avalonia;
@ -21,9 +22,9 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeViewModel : ActivatableViewModelBase public class NodeViewModel : ActivatableViewModelBase
{ {
private readonly INodeEditorService _nodeEditorService; private readonly INodeEditorService _nodeEditorService;
private readonly IWindowService _windowService;
private ICustomNodeViewModel? _customNodeViewModel; private ICustomNodeViewModel? _customNodeViewModel;
private ReactiveCommand<Unit, Unit>? _deleteNode;
private double _dragOffsetX; private double _dragOffsetX;
private double _dragOffsetY; private double _dragOffsetY;
private ObservableAsPropertyHelper<bool>? _hasInputPins; private ObservableAsPropertyHelper<bool>? _hasInputPins;
@ -34,14 +35,13 @@ public class NodeViewModel : ActivatableViewModelBase
private double _startX; private double _startX;
private double _startY; 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; _nodeEditorService = nodeEditorService;
_windowService = windowService;
NodeScriptViewModel = nodeScriptViewModel; NodeScriptViewModel = nodeScriptViewModel;
Node = node; Node = node;
DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v));
SourceList<PinViewModel> nodePins = new(); SourceList<PinViewModel> nodePins = new();
SourceList<PinCollectionViewModel> nodePinCollections = new(); SourceList<PinCollectionViewModel> nodePinCollections = new();
@ -62,6 +62,9 @@ public class NodeViewModel : ActivatableViewModelBase
PinViewModels = pins; PinViewModels = pins;
DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v));
ShowBrokenState = ReactiveCommand.Create(ExecuteShowBrokenState);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode) _isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode)
@ -152,18 +155,15 @@ public class NodeViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _customNodeViewModel, value); set => RaiseAndSetIfChanged(ref _customNodeViewModel, value);
} }
public ReactiveCommand<Unit, Unit>? DeleteNode
{
get => _deleteNode;
set => RaiseAndSetIfChanged(ref _deleteNode, value);
}
public bool IsSelected public bool IsSelected
{ {
get => _isSelected; get => _isSelected;
set => RaiseAndSetIfChanged(ref _isSelected, value); set => RaiseAndSetIfChanged(ref _isSelected, value);
} }
public ReactiveCommand<Unit,Unit> ShowBrokenState { get; }
public ReactiveCommand<Unit, Unit> DeleteNode { get; }
public void StartDrag(Point mouseStartPosition) public void StartDrag(Point mouseStartPosition)
{ {
if (!IsSelected) if (!IsSelected)
@ -195,4 +195,10 @@ public class NodeViewModel : ActivatableViewModelBase
{ {
_nodeEditorService.ExecuteCommand(NodeScriptViewModel.NodeScript, new DeleteNode(NodeScriptViewModel.NodeScript, Node)); _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);
}
} }

View File

@ -8,7 +8,7 @@ public class StringRegexMatchNode : Node
{ {
private string? _lastPattern; private string? _lastPattern;
private Regex? _regex; private Regex? _regex;
private bool _broken; private Exception? _exception;
public StringRegexMatchNode() : base("Regex Match", "Checks provided regex pattern matches the input.") 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) if (Input.Value == null || Pattern.Value == null)
return; 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) if (_regex == null || _lastPattern != Pattern.Value)
{ {
try try
{ {
_regex = new Regex(Pattern.Value, RegexOptions.Compiled); _regex = new Regex(Pattern.Value, RegexOptions.Compiled);
_broken = false; _exception = null;
} }
catch (Exception) catch (Exception e)
{ {
_broken = true; // If there is an exception, save it to keep rethrowing until the regex is fixed
return; _exception = e;
throw;
} }
finally finally
{ {