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:
parent
0120f37c93
commit
88f01abe0d
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,18 +67,16 @@ public class LinuxInputProvider : InputProvider
|
||||
|
||||
private void HandleKeyboardData(LinuxInputDevice keyboard, LinuxInputEventArgs args)
|
||||
{
|
||||
switch (args.Type)
|
||||
{
|
||||
case LinuxInputEventType.KEY:
|
||||
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
|
||||
@ -88,16 +89,12 @@ public class LinuxInputProvider : InputProvider
|
||||
}
|
||||
|
||||
OnKeyboardDataReceived(device, key, isDown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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)
|
||||
@ -73,33 +113,11 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
|
||||
}
|
||||
}
|
||||
|
||||
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 />
|
||||
|
||||
@ -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 />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user