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:
parent
cd537051ca
commit
eca8985fc4
@ -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();
|
||||||
|
|||||||
@ -115,7 +115,7 @@ internal class NodeService : INodeService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Initialize(script);
|
node.TryInitialize(script);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -101,7 +101,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
|
|||||||
node.Reset();
|
node.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExitNode.Evaluate();
|
ExitNode.TryEvaluate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user