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

Data model node - Fixed pins disconnecting after restart

Data model event node - Fixed pins disconnecting after restart
Node editor - Fixed connecting already connected input pins to an output pin causing a crash
Node editor - Added auto-save every 2 minutes while nodes are open (profiles are already auto-saved whenever you make a change, nodes will get the same treatment later)
This commit is contained in:
Robert 2022-08-30 17:22:36 +02:00
parent 0120f37c93
commit 88f01abe0d
10 changed files with 156 additions and 99 deletions

View File

@ -11,17 +11,15 @@ namespace Artemis.Core.Services;
internal class ProcessMonitorService : IProcessMonitorService
{
private readonly ProcessComparer _comparer;
private readonly ILogger _logger;
private Process[] _lastScannedProcesses;
public ProcessMonitorService(ILogger logger)
public ProcessMonitorService()
{
_logger = logger;
_comparer = new ProcessComparer();
_lastScannedProcesses = Process.GetProcesses();
Timer processScanTimer = new(1000);
processScanTimer.Elapsed += OnTimerElapsed;
processScanTimer.Start();
_comparer = new ProcessComparer();
ProcessActivationRequirement.ProcessMonitorService = this;
}
@ -30,16 +28,9 @@ internal class ProcessMonitorService : IProcessMonitorService
{
Process[] newProcesses = Process.GetProcesses();
foreach (Process startedProcess in newProcesses.Except(_lastScannedProcesses, _comparer))
{
ProcessStarted?.Invoke(this, new ProcessEventArgs(startedProcess));
//_logger.Verbose("Started Process: {startedProcess}", startedProcess.ProcessName);
}
foreach (Process stoppedProcess in _lastScannedProcesses.Except(newProcesses, _comparer))
{
ProcessStopped?.Invoke(this, new ProcessEventArgs(stoppedProcess));
//_logger.Verbose("Stopped Process: {stoppedProcess}", stoppedProcess.ProcessName);
}
_lastScannedProcesses = newProcesses;
}

View File

@ -8,7 +8,7 @@ namespace Artemis.Core;
/// <summary>
/// Represents a node script
/// </summary>
public interface INodeScript : INotifyPropertyChanged, IDisposable
public interface INodeScript : INotifyPropertyChanged, IDisposable, IStorageModel
{
/// <summary>
/// Gets the name of the node script.
@ -66,6 +66,11 @@ public interface INodeScript : INotifyPropertyChanged, IDisposable
/// </summary>
/// <param name="node">The node to remove</param>
void RemoveNode(INode node);
/// <summary>
/// Loads missing connections between the nodes of this node script from storage
/// </summary>
void LoadConnections();
}
/// <summary>

View File

@ -12,7 +12,7 @@ namespace Artemis.Core;
/// <summary>
/// Represents a node script
/// </summary>
public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageModel
public abstract class NodeScript : CorePropertyChanged, INodeScript
{
private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e)
{
@ -226,9 +226,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageMod
return node;
}
/// <summary>
/// Loads missing connections between the nodes of this node script from the <see cref="Entity" />
/// </summary>
/// <inheritdoc />
public void LoadConnections()
{
List<INode> nodes = Nodes.ToList();

View File

@ -33,12 +33,14 @@ public class LinuxInputProvider : InputProvider
protected override void Dispose(bool disposing)
{
if (disposing)
{
for (int i = _readers.Count - 1; i >= 0; i--)
{
_readers[i].InputEvent -= OnInputEvent;
_readers[i].Dispose();
_readers.RemoveAt(i);
}
}
base.Dispose(disposing);
}
@ -49,6 +51,7 @@ public class LinuxInputProvider : InputProvider
{
if (sender is not LinuxInputDeviceReader reader)
return;
switch (reader.InputDevice.DeviceType)
{
case LinuxDeviceType.Keyboard:
@ -64,40 +67,34 @@ public class LinuxInputProvider : InputProvider
private void HandleKeyboardData(LinuxInputDevice keyboard, LinuxInputEventArgs args)
{
switch (args.Type)
if (args.Type != LinuxInputEventType.KEY)
return;
KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) args.Code);
bool isDown = args.Value != 0;
//_logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes)args.Code} | Down: {isDown}");
LinuxInputDevice.LinuxInputId identifier = keyboard.InputId;
OnIdentifierReceived(identifier, InputDeviceType.Keyboard);
ArtemisDevice? device = null;
try
{
case LinuxInputEventType.KEY:
KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) args.Code);
bool isDown = args.Value != 0;
//_logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes)args.Code} | Down: {isDown}");
LinuxInputDevice.LinuxInputId identifier = keyboard.InputId;
OnIdentifierReceived(identifier, InputDeviceType.Keyboard);
ArtemisDevice? device = null;
try
{
device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard);
}
catch (Exception e)
{
_logger.Warning(e, "Failed to retrieve input device by its identifier");
}
OnKeyboardDataReceived(device, key, isDown);
break;
device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard);
}
catch (Exception e)
{
_logger.Warning(e, "Failed to retrieve input device by its identifier");
}
OnKeyboardDataReceived(device, key, isDown);
}
private void HandleMouseData(LinuxInputDevice mouse, LinuxInputEventArgs args)
{
LinuxInputDevice.LinuxInputId identifier = mouse.InputId;
OnIdentifierReceived(identifier, InputDeviceType.Mouse);
ArtemisDevice? device = null;
try
@ -112,13 +109,11 @@ public class LinuxInputProvider : InputProvider
switch (args.Type)
{
case LinuxInputEventType.KEY:
var key = (LinuxKeyboardKeyCodes)args.Code;
if (key == LinuxKeyboardKeyCodes.BTN_TOUCH ||
LinuxKeyboardKeyCodes key = (LinuxKeyboardKeyCodes) args.Code;
if (key == LinuxKeyboardKeyCodes.BTN_TOUCH ||
(key >= LinuxKeyboardKeyCodes.BTN_TOOL_PEN && key <= LinuxKeyboardKeyCodes.BTN_TOOL_QUADTAP))
{
//trackpad input, ignore.
return;
}
//0 - up
//1 - down

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
@ -8,9 +9,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
/// </summary>
public class ConnectPins : INodeEditorCommand
{
private readonly List<IPin>? _originalConnections;
private readonly IPin _source;
private readonly IPin _target;
private readonly IPin _output;
private readonly IPin _input;
private readonly IPin? _originalConnection;
/// <summary>
/// Creates a new instance of the <see cref="ConnectPins" /> class.
@ -19,10 +20,18 @@ public class ConnectPins : INodeEditorCommand
/// <param name="target">The target of the connection.</param>
public ConnectPins(IPin source, IPin target)
{
_source = source;
_target = target;
if (source.Direction == PinDirection.Output)
{
_output = source;
_input = target;
}
else
{
_output = target;
_input = source;
}
_originalConnections = _target.Direction == PinDirection.Input ? new List<IPin>(_target.ConnectedTo) : null;
_originalConnection = _input.ConnectedTo.FirstOrDefault();
}
#region Implementation of INodeEditorCommand
@ -33,20 +42,16 @@ public class ConnectPins : INodeEditorCommand
/// <inheritdoc />
public void Execute()
{
if (_target.Direction == PinDirection.Input)
_target.DisconnectAll();
_source.ConnectTo(_target);
_input.DisconnectAll();
_output.ConnectTo(_input);
}
/// <inheritdoc />
public void Undo()
{
_target.DisconnectFrom(_source);
if (_originalConnections == null)
return;
foreach (IPin pin in _originalConnections)
_target.ConnectTo(pin);
_input.DisconnectAll();
if (_originalConnection != null)
_input.ConnectTo(_originalConnection);
}
#endregion

View File

@ -25,6 +25,7 @@ public class DataBindingViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
private bool _playing;
private bool _editorOpen;
public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService)
{
@ -49,11 +50,18 @@ public class DataBindingViewModel : ActivatableViewModelBase
.DisposeWith(d);
_profileEditorService.Playing.CombineLatest(_profileEditorService.SuspendedEditing).Subscribe(tuple => _playing = tuple.First || tuple.Second).DisposeWith(d);
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update);
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Render, Update);
// TODO: Remove in favor of saving each time a node editor command is executed
DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save);
updateTimer.Start();
saveTimer.Start();
Disposable.Create(() =>
{
updateTimer.Stop();
saveTimer.Stop();
_profileEditorService.SaveProfile();
}).DisposeWith(d);
});
@ -74,11 +82,19 @@ public class DataBindingViewModel : ActivatableViewModelBase
public async Task OpenEditor()
{
if (LayerProperty != null && LayerProperty.BaseDataBinding.IsEnabled)
if (LayerProperty == null || !LayerProperty.BaseDataBinding.IsEnabled)
return;
try
{
_editorOpen = true;
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", LayerProperty.BaseDataBinding.Script));
await _profileEditorService.SaveProfileAsync();
}
finally
{
_editorOpen = false;
}
}
private void Update(object? sender, EventArgs e)
@ -89,4 +105,10 @@ public class DataBindingViewModel : ActivatableViewModelBase
LayerProperty?.UpdateDataBinding();
}
private void Save(object? sender, EventArgs e)
{
if (!_editorOpen)
_profileEditorService.SaveProfile();
}
}

View File

@ -12,6 +12,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Threading;
using DynamicData;
@ -25,6 +26,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
private readonly INodeEditorService _nodeEditorService;
private readonly INodeService _nodeService;
private readonly ISettingsService _settingsService;
private readonly IProfileService _profileService;
private readonly IWindowService _windowService;
public NodeScriptWindowViewModel(NodeScript nodeScript,
@ -32,6 +34,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
INodeEditorService nodeEditorService,
INodeVmFactory vmFactory,
ISettingsService settingsService,
IProfileService profileService,
IWindowService windowService)
{
NodeScript = nodeScript;
@ -43,6 +46,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
_nodeService = nodeService;
_nodeEditorService = nodeEditorService;
_settingsService = settingsService;
_profileService = profileService;
_windowService = windowService;
SourceList<NodeData> nodeSourceList = new();
@ -60,8 +64,17 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
this.WhenActivated(d =>
{
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Normal, Update);
// TODO: Remove in favor of saving each time a node editor command is executed
DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save);
updateTimer.Start();
Disposable.Create(() => updateTimer.Stop()).DisposeWith(d);
saveTimer.Start();
Disposable.Create(() =>
{
updateTimer.Stop();
saveTimer.Stop();
}).DisposeWith(d);
});
}
@ -133,4 +146,10 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
{
NodeScript.Run();
}
private void Save(object? sender, EventArgs e)
{
if (NodeScript.Context is Profile profile)
_profileService.SaveProfile(profile, true);
}
}

View File

@ -48,9 +48,7 @@ public class DataModelEventCycleNode : Node<DataModelPathEntity, DataModelEventC
public override void Evaluate()
{
object? pathValue = _dataModelPath?.GetValue();
bool hasTriggered = pathValue is IDataModelEvent dataModelEvent
? EvaluateEvent(dataModelEvent)
: EvaluateValue(pathValue);
bool hasTriggered = pathValue is IDataModelEvent dataModelEvent ? EvaluateEvent(dataModelEvent) : EvaluateValue(pathValue);
if (hasTriggered)
{

View File

@ -1,7 +1,6 @@
using System.Linq.Expressions;
using System.Reflection;
using Artemis.Core;
using Artemis.Core.Events;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.DataModel.Screens;
@ -41,6 +40,47 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
UpdateDataModelPath();
}
public override void Evaluate()
{
object? pathValue = _dataModelPath?.GetValue();
if (pathValue is not IDataModelEvent dataModelEvent)
return;
TimeSinceLastTrigger.Value = new Numeric(dataModelEvent.TimeSinceLastTrigger.TotalMilliseconds);
TriggerCount.Value = new Numeric(dataModelEvent.TriggerCount);
foreach ((Func<DataModelEventArgs, object> propertyAccessor, OutputPin outputPin) in _propertyPins)
{
if (!outputPin.ConnectedTo.Any())
continue;
object value = dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!;
outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value;
}
}
private void UpdateDataModelPath()
{
DataModelPath? old = _dataModelPath;
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
if (_dataModelPath != null)
_dataModelPath.PathValidated += DataModelPathOnPathValidated;
if (old != null)
{
old.PathValidated -= DataModelPathOnPathValidated;
old.Dispose();
}
UpdateOutputPins();
}
private void UpdateOutputPins()
{
object? pathValue = _dataModelPath?.GetValue();
if (pathValue is IDataModelEvent dataModelEvent)
CreatePins(dataModelEvent);
}
private void CreatePins(IDataModelEvent? dataModelEvent)
{
if (_dataModelEvent == dataModelEvent)
@ -72,34 +112,12 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
_propertyPins.Add(expression, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
}
}
public override void Evaluate()
private void DataModelPathOnPathValidated(object? sender, EventArgs e)
{
object? pathValue = _dataModelPath?.GetValue();
if (pathValue is not IDataModelEvent dataModelEvent)
return;
TimeSinceLastTrigger.Value = new Numeric(dataModelEvent.TimeSinceLastTrigger.TotalMilliseconds);
TriggerCount.Value = new Numeric(dataModelEvent.TriggerCount);
foreach ((Func<DataModelEventArgs, object> propertyAccessor, OutputPin outputPin) in _propertyPins)
{
if (!outputPin.ConnectedTo.Any())
continue;
object value = dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!;
outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value;
}
}
private void UpdateDataModelPath()
{
DataModelPath? old = _dataModelPath;
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
old?.Dispose();
object? pathValue = _dataModelPath?.GetValue();
if (pathValue is IDataModelEvent dataModelEvent)
CreatePins(dataModelEvent);
// Update the output pin now that the type is known and attempt to restore the connection that was likely missing
UpdateOutputPins();
Script?.LoadConnections();
}
/// <inheritdoc />

View File

@ -1,7 +1,6 @@
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.DataModel.Screens;
using Avalonia.Threading;
namespace Artemis.VisualScripting.Nodes.DataModel;
@ -61,17 +60,24 @@ public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewMo
private void UpdateDataModelPath()
{
DataModelPath? old = _dataModelPath;
old?.Dispose();
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
if (_dataModelPath != null)
_dataModelPath.PathValidated += DataModelPathOnPathValidated;
if (old != null)
{
old.PathValidated -= DataModelPathOnPathValidated;
old.Dispose();
}
UpdateOutputPin();
}
private void DataModelPathOnPathValidated(object? sender, EventArgs e)
{
Dispatcher.UIThread.InvokeAsync(UpdateOutputPin);
// Update the output pin now that the type is known and attempt to restore the connection that was likely missing
UpdateOutputPin();
Script?.LoadConnections();
}
/// <inheritdoc />