From a4667fdc037d8ab3d238eb376ef1248d2270ab13 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 21 Aug 2021 13:51:25 +0200 Subject: [PATCH] Profiles - Only load nodes after the entire profile structure loaded Profiles - Added API to get all render elements Nodes - Added layer property node --- .../Profile/DataBindings/DataBinding.cs | 4 + .../DataBindings/DataBindingRegistration.cs | 2 +- .../Profile/DataBindings/IDataBinding.cs | 5 + src/Artemis.Core/Models/Profile/Profile.cs | 17 ++- .../Models/Profile/ProfileElement.cs | 79 +++++++---- .../Models/Profile/RenderProfileElement.cs | 17 ++- .../Services/Storage/ProfileService.cs | 14 +- .../Visualization/ProfileViewModel.cs | 8 +- .../LayerPropertyNodeCustomViewModel.cs | 80 +++++++++++ .../LayerPropertyNodeCustomView.xaml | 18 +++ .../Nodes/LayerPropertyNode.cs | 130 ++++++++++++++++++ 11 files changed, 317 insertions(+), 57 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml create mode 100644 src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 0cf5fc9fb..e2392c79e 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -242,7 +242,11 @@ namespace Artemis.Core EasingTime = Entity.EasingTime; EasingFunction = (Easings.Functions) Entity.EasingFunction; + } + /// + public void LoadNodeScript() + { Script.Dispose(); Script = Entity.NodeScript != null ? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index cab335a43..e7d5ca1d1 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -67,7 +67,7 @@ namespace Artemis.Core DataBinding = new DataBinding(LayerProperty, dataBinding); return DataBinding; } - + /// public void ClearDataBinding() { diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs index 1f54ca146..3c7c995bd 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -19,5 +19,10 @@ namespace Artemis.Core /// Applies the data binding to the layer property /// void Apply(); + + /// + /// If the data binding is enabled, loads the node script for that data binding + /// + void LoadNodeScript(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index c0c3a2528..a9090e12d 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -206,12 +206,10 @@ namespace Artemis.Core } } + List renderElements = GetAllRenderElements(); + if (ProfileEntity.LastSelectedProfileElement != Guid.Empty) - { - LastSelectedProfileElement = GetAllFolders().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); - if (LastSelectedProfileElement == null) - LastSelectedProfileElement = GetAllLayers().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); - } + LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); else LastSelectedProfileElement = null; @@ -219,6 +217,10 @@ namespace Artemis.Core scriptConfiguration.Script?.Dispose(); ScriptConfigurations.Clear(); ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); + + // Load node scripts last since they may rely on the profile structure being in place + foreach (RenderProfileElement renderProfileElement in renderElements) + renderProfileElement.LoadNodeScript(); } internal override void Save() @@ -253,10 +255,7 @@ namespace Artemis.Core /// public override IEnumerable GetBrokenHierarchy() { - foreach (IBreakableModel breakableModel in GetAllFolders().SelectMany(folders => folders.GetBrokenHierarchy())) - yield return breakableModel; - foreach (IBreakableModel breakableModel in GetAllLayers().SelectMany(layer => layer.GetBrokenHierarchy())) - yield return breakableModel; + return GetAllRenderElements().SelectMany(folders => folders.GetBrokenHierarchy()); } #endregion diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 81537b0ba..a36dea794 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -99,6 +99,13 @@ namespace Artemis.Core /// public bool Disposed { get; protected set; } + #region Overrides of BreakableModel + + /// + public override string BrokenDisplayName => Name ?? GetType().Name; + + #endregion + /// /// Updates the element /// @@ -121,13 +128,6 @@ namespace Artemis.Core return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; } - #region Overrides of BreakableModel - - /// - public override string BrokenDisplayName => Name ?? GetType().Name; - - #endregion - #region Hierarchy /// @@ -147,7 +147,9 @@ namespace Artemis.Core // Add to the end of the list if (order == null) + { ChildrenList.Add(child); + } // Insert at the given index else { @@ -191,6 +193,27 @@ namespace Artemis.Core ChildrenList[index].Order = index; } + /// + /// Returns a flattened list of all child render elements + /// + /// + public List GetAllRenderElements() + { + if (Disposed) + throw new ObjectDisposedException(GetType().Name); + + List elements = new(); + foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast()) + { + // Add all folders in this element + elements.Add(childElement); + // Add all folders in folders inside this element + elements.AddRange(childElement.GetAllRenderElements()); + } + + return elements; + } + /// /// Returns a flattened list of all child folders /// @@ -240,27 +263,6 @@ namespace Artemis.Core internal abstract void Load(); internal abstract void Save(); - #endregion - - #region IDisposable - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes the profile element - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - #endregion #region Events @@ -292,5 +294,26 @@ namespace Artemis.Core } #endregion + + #region IDisposable + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the profile element + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 7003dd985..f6a87f5b0 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -70,10 +70,6 @@ namespace Artemis.Core internal void LoadRenderElement() { - DisplayCondition = RenderElementEntity.NodeScript != null - ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) - : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); - Timeline = RenderElementEntity.Timeline != null ? new Timeline(RenderElementEntity.Timeline) : new Timeline(); @@ -111,6 +107,19 @@ namespace Artemis.Core Timeline?.Save(); } + internal void LoadNodeScript() + { + DisplayCondition = RenderElementEntity.NodeScript != null + ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) + : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); + + foreach (ILayerProperty layerProperty in GetAllLayerProperties()) + { + foreach (IDataBindingRegistration dataBindingRegistration in layerProperty.GetAllDataBindingRegistrations()) + dataBindingRegistration.GetDataBinding()?.LoadNodeScript(); + } + } + internal void OnLayerEffectsUpdated() { LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 3f532faf3..b6221c19f 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -438,10 +438,8 @@ namespace Artemis.Core.Services profile.Save(); if (includeChildren) { - foreach (Folder folder in profile.GetAllFolders()) - folder.Save(); - foreach (Layer layer in profile.GetAllLayers()) - layer.Save(); + foreach (RenderProfileElement child in profile.GetAllRenderElements()) + child.Save(); } // If there are no changes, don't bother saving @@ -599,11 +597,9 @@ namespace Artemis.Core.Services profile.Save(); - foreach (Folder folder in profile.GetAllFolders()) - folder.Save(); - foreach (Layer layer in profile.GetAllLayers()) - layer.Save(); - + foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements()) + renderProfileElement.Save(); + _logger.Debug("Adapt profile - Saving " + profile); profile.RedoStack.Clear(); profile.UndoStack.Push(memento); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 6f4fea3d0..b0549cdd7 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -313,15 +313,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null) return; - foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() + foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllRenderElements() .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) .SelectMany(p => p.GetAllDataBindingRegistrations())) dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); - foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllLayers() - .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) - .SelectMany(p => p.GetAllDataBindingRegistrations())) - dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); - + // TODO: Only update when there are data bindings if (!_profileEditorService.Playing) _profileEditorService.UpdateProfilePreview(); diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs new file mode 100644 index 000000000..edf84d7e1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Core; +using Stylet; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels +{ + public class LayerPropertyNodeCustomViewModel : CustomNodeViewModel + { + private readonly LayerPropertyNode _node; + + private RenderProfileElement _selectedProfileElement; + private ILayerProperty _selectedLayerProperty; + + public LayerPropertyNodeCustomViewModel(LayerPropertyNode node) : base(node) + { + _node = node; + } + + public BindableCollection ProfileElements { get; } = new(); + + public RenderProfileElement SelectedProfileElement + { + get => _selectedProfileElement; + set + { + if (!SetAndNotify(ref _selectedProfileElement, value)) return; + _node.ChangeProfileElement(_selectedProfileElement); + GetLayerProperties(); + } + } + + public BindableCollection LayerProperties { get; } = new(); + + public ILayerProperty SelectedLayerProperty + { + get => _selectedLayerProperty; + set + { + if (!SetAndNotify(ref _selectedLayerProperty, value)) return; + _node.ChangeLayerProperty(_selectedLayerProperty); + } + } + + private void GetProfileElements() + { + ProfileElements.Clear(); + if (_node.Script.Context is not Profile profile) + return; + + List elements = new(profile.GetAllRenderElements()); + + ProfileElements.AddRange(elements.OrderBy(e => e.Order)); + _selectedProfileElement = _node.ProfileElement; + NotifyOfPropertyChange(nameof(SelectedProfileElement)); + } + + private void GetLayerProperties() + { + LayerProperties.Clear(); + if (_node.ProfileElement == null) + return; + + LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.GetAllDataBindingRegistrations().Any())); + _selectedLayerProperty = _node.LayerProperty; + NotifyOfPropertyChange(nameof(SelectedLayerProperty)); + } + + #region Overrides of CustomNodeViewModel + + /// + protected override void OnDisplay() + { + GetProfileElements(); + GetLayerProperties(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml new file mode 100644 index 000000000..42be19cb8 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs new file mode 100644 index 000000000..64f75d022 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes +{ + [Node("Layer/Folder Property", "Outputs the property of a selected layer or folder")] + public class LayerPropertyNode : Node + { + private readonly object _layerPropertyLock = new(); + + public INodeScript Script { get; private set; } + public RenderProfileElement ProfileElement { get; private set; } + public ILayerProperty LayerProperty { get; private set; } + + public override void Evaluate() + { + lock (_layerPropertyLock) + { + // In this case remove the pins so no further evaluations occur + if (LayerProperty == null) + { + CreatePins(); + return; + } + + List list = LayerProperty.GetAllDataBindingRegistrations(); + int index = 0; + foreach (IPin pin in Pins) + { + OutputPin outputPin = (OutputPin) pin; + IDataBindingRegistration dataBindingRegistration = list[index]; + index++; + + // TODO: Is this really non-nullable? + outputPin.Value = dataBindingRegistration.GetValue(); + } + } + } + + public override void Initialize(INodeScript script) + { + Script = script; + + if (script.Context is Profile profile) + profile.ChildRemoved += ProfileOnChildRemoved; + + LoadLayerProperty(); + } + + public void LoadLayerProperty() + { + lock (_layerPropertyLock) + { + if (Script.Context is not Profile profile) + return; + if (Storage is not LayerPropertyNodeEntity entity) + return; + + RenderProfileElement element = profile.GetAllRenderElements().FirstOrDefault(l => l.EntityId == entity.ElementId); + + ProfileElement = element; + LayerProperty = element?.GetAllLayerProperties().FirstOrDefault(p => p.Path == entity.PropertyPath); + CreatePins(); + } + } + + public void ChangeProfileElement(RenderProfileElement profileElement) + { + lock (_layerPropertyLock) + { + ProfileElement = profileElement; + LayerProperty = null; + + Storage = new LayerPropertyNodeEntity + { + ElementId = ProfileElement?.EntityId ?? Guid.Empty, + PropertyPath = null + }; + + CreatePins(); + } + } + + public void ChangeLayerProperty(ILayerProperty layerProperty) + { + lock (_layerPropertyLock) + { + LayerProperty = layerProperty; + + Storage = new LayerPropertyNodeEntity + { + ElementId = ProfileElement?.EntityId ?? Guid.Empty, + PropertyPath = LayerProperty?.Path + }; + + CreatePins(); + } + } + + private void CreatePins() + { + while (Pins.Any()) + RemovePin((Pin) Pins.First()); + + if (LayerProperty == null) + return; + + foreach (IDataBindingRegistration dataBindingRegistration in LayerProperty.GetAllDataBindingRegistrations()) + CreateOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName); + } + + private void ProfileOnChildRemoved(object? sender, EventArgs e) + { + if (Script.Context is not Profile profile) + return; + + if (!profile.GetAllRenderElements().Contains(ProfileElement)) + ChangeProfileElement(null); + } + } + + public class LayerPropertyNodeEntity + { + public Guid ElementId { get; set; } + public string PropertyPath { get; set; } + } +} \ No newline at end of file