From 8cba36f5d4816b28b9c69c07690de910482dbde0 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 18 Apr 2021 22:22:16 +0200 Subject: [PATCH 1/5] Dynamic data model - Removed DataModel constraint Dynamic data model - Added ability to provide custom description attribute --- ...s.cs => DynamicDataModelChildEventArgs.cs} | 12 +- .../Profile/DataModel/DataModelPathSegment.cs | 37 ++- .../Plugins/DataModelExpansions/DataModel.cs | 223 ++++++++++++------ .../Shared/DataModelVisualizationViewModel.cs | 6 +- 4 files changed, 182 insertions(+), 96 deletions(-) rename src/Artemis.Core/Events/{DynamicDataModelEventArgs.cs => DynamicDataModelChildEventArgs.cs} (53%) diff --git a/src/Artemis.Core/Events/DynamicDataModelEventArgs.cs b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs similarity index 53% rename from src/Artemis.Core/Events/DynamicDataModelEventArgs.cs rename to src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs index c1b335adc..61e69c6d1 100644 --- a/src/Artemis.Core/Events/DynamicDataModelEventArgs.cs +++ b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs @@ -4,20 +4,20 @@ using Artemis.Core.DataModelExpansions; namespace Artemis.Core { /// - /// Provides data about dynamic data model related events + /// Provides data about dynamic data model child related events /// - public class DynamicDataModelEventArgs : EventArgs + public class DynamicDataModelChildEventArgs : EventArgs { - internal DynamicDataModelEventArgs(DataModel dynamicDataModel, string key) + internal DynamicDataModelChildEventArgs(object? dynamicChild, string key) { - DynamicDataModel = dynamicDataModel; + DynamicChild = dynamicChild; Key = key; } /// - /// Gets the dynamic data model + /// Gets the dynamic data model child /// - public DataModel DynamicDataModel { get; } + public object? DynamicChild { get; } /// /// Gets the key of the dynamic data model on the parent diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index c8fd98d79..f78bc7c54 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -15,6 +15,8 @@ namespace Artemis.Core { private Expression>? _accessorLambda; private DataModel? _dynamicDataModel; + private Type _dynamicDataModelType; + private DataModelPropertyAttribute _dynamicDataModelAttribute; internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path) { @@ -49,12 +51,6 @@ namespace Artemis.Core /// public DataModelPathSegmentType Type { get; private set; } - /// - /// Gets the type of dynamic data model this path points to - /// Not used if the is - /// - public Type? DynamicDataModelType { get; private set; } - /// /// Gets the previous segment in the path /// @@ -114,7 +110,7 @@ namespace Artemis.Core { // Dynamic types have a data model description if (Type == DataModelPathSegmentType.Dynamic) - return (GetValue() as DataModel)?.DataModelDescription; + return _dynamicDataModelAttribute; if (IsStartSegment && DataModelPath.Target != null) return DataModelPath.Target.DataModelDescription; if (IsStartSegment) @@ -187,12 +183,12 @@ namespace Artemis.Core return CreateExpression(parameter, expression, nullCondition); // If a dynamic data model is found the use that - bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel? dynamicDataModel); - if (hasDynamicDataModel && dynamicDataModel != null) - DetermineDynamicType(dynamicDataModel); + bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild); + if (hasDynamicChild && dynamicChild?.Value != null) + DetermineDynamicType(dynamicChild.Value, dynamicChild.Attribute); - _dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded; - _dynamicDataModel.DynamicDataModelRemoved += DynamicDataModelOnDynamicDataModelRemoved; + _dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded; + _dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved; } return CreateExpression(parameter, expression, nullCondition); @@ -219,7 +215,7 @@ namespace Artemis.Core accessorExpression = Expression.Call( expression, nameof(DataModel.DynamicChild), - DynamicDataModelType != null ? new[] {DynamicDataModelType} : null, + _dynamicDataModelType != null ? new[] { _dynamicDataModelType } : null, Expression.Constant(Identifier) ); @@ -236,10 +232,11 @@ namespace Artemis.Core return accessorExpression; } - private void DetermineDynamicType(DataModel dynamicDataModel) + private void DetermineDynamicType(object dynamicDataModel, DataModelPropertyAttribute attribute) { Type = DataModelPathSegmentType.Dynamic; - DynamicDataModelType = dynamicDataModel.GetType(); + _dynamicDataModelType = dynamicDataModel.GetType(); + _dynamicDataModelAttribute = attribute; } private void DetermineStaticType(Type previousType) @@ -263,8 +260,8 @@ namespace Artemis.Core { if (_dynamicDataModel != null) { - _dynamicDataModel.DynamicDataModelAdded -= DynamicDataModelOnDynamicDataModelAdded; - _dynamicDataModel.DynamicDataModelRemoved -= DynamicDataModelOnDynamicDataModelRemoved; + _dynamicDataModel.DynamicChildAdded -= DynamicChildOnDynamicChildAdded; + _dynamicDataModel.DynamicChildRemoved -= DynamicChildOnDynamicChildRemoved; } Type = DataModelPathSegmentType.Invalid; @@ -285,15 +282,15 @@ namespace Artemis.Core #region Event handlers - private void DynamicDataModelOnDynamicDataModelAdded(object? sender, DynamicDataModelEventArgs e) + private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e) { if (e.Key == Identifier) DataModelPath.Initialize(); } - private void DynamicDataModelOnDynamicDataModelRemoved(object? sender, DynamicDataModelEventArgs e) + private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) { - if (e.DynamicDataModel == _dynamicDataModel) + if (e.DynamicChild == _dynamicDataModel) DataModelPath.Initialize(); } diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index e207c18eb..392995a4c 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -14,7 +14,7 @@ namespace Artemis.Core.DataModelExpansions /// public abstract class DataModel { - private readonly Dictionary _dynamicDataModels = new(); + private readonly Dictionary _dynamicChildren = new(); /// /// Creates a new instance of the class @@ -47,10 +47,10 @@ namespace Artemis.Core.DataModelExpansions public bool IsExpansion { get; internal set; } /// - /// Gets an read-only dictionary of all dynamic data models + /// Gets an read-only dictionary of all dynamic children /// [DataModelIgnore] - public ReadOnlyDictionary DynamicDataModels => new(_dynamicDataModels); + public ReadOnlyDictionary DynamicChildren => new(_dynamicChildren); /// /// Returns a read-only collection of all properties in this datamodel that are to be ignored @@ -67,124 +67,211 @@ namespace Artemis.Core.DataModelExpansions } /// - /// Adds a dynamic data model to this data model + /// Adds a dynamic child to this data model /// - /// The dynamic data model to add + /// The dynamic child to add /// The key of the child, must be unique to this data model - /// An optional name, if not provided the key will be used in a humanized form - /// An optional description - public T AddDynamicChild(T dynamicDataModel, string key, string? name = null, string? description = null) where T : DataModel + /// An optional human readable name, if not provided the key will be used in a humanized form + public T AddDynamicChild(T dynamicChild, string key, string? name = null) { - if (dynamicDataModel == null) - throw new ArgumentNullException(nameof(dynamicDataModel)); + if (dynamicChild == null) + throw new ArgumentNullException(nameof(dynamicChild)); if (key == null) throw new ArgumentNullException(nameof(key)); if (key.Contains('.')) throw new ArtemisCoreException("The provided key contains an illegal character (.)"); - if (_dynamicDataModels.ContainsKey(key)) - throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + - "because the key is already in use on by another dynamic property this data model."); - - if (_dynamicDataModels.ContainsValue(dynamicDataModel)) + if (_dynamicChildren.ContainsKey(key)) { - string existingKey = _dynamicDataModels.First(kvp => kvp.Value == dynamicDataModel).Key; - throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + - $"because the dynamic data model is already added with key '{existingKey}."); + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); } if (GetType().GetProperty(key) != null) - throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + "because the key is already in use by a static property on this data model."); + } - dynamicDataModel.Feature = Feature; - dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute + DataModelPropertyAttribute attribute = new() + { + Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name + }; + if (dynamicChild is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } + + _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; + } + + /// + /// Adds a dynamic child to this data model + /// + /// The dynamic child to add + /// The key of the child, must be unique to this data model + /// A human readable for your dynamic child, shown in the UI + /// An optional description, shown in the UI + public T AddDynamicChild(T dynamicChild, string key, string name, string description) + { + if (dynamicChild == null) + throw new ArgumentNullException(nameof(dynamicChild)); + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (key.Contains('.')) + throw new ArtemisCoreException("The provided key contains an illegal character (.)"); + if (_dynamicChildren.ContainsKey(key)) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); + } + + if (GetType().GetProperty(key) != null) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use by a static property on this data model."); + } + + DataModelPropertyAttribute attribute = new() { Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name, Description = description }; - _dynamicDataModels.Add(key, dynamicDataModel); + if (dynamicChild is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } - OnDynamicDataModelAdded(new DynamicDataModelEventArgs(dynamicDataModel, key)); - return dynamicDataModel; + _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; } /// - /// Removes a dynamic data model from the data model by its key + /// Adds a dynamic child to this data model /// - /// The key of the dynamic data model to remove + /// The dynamic child to add + /// The key of the child, must be unique to this data model + /// A data model property attribute describing the dynamic child + public T AddDynamicChild(T dynamicChild, string key, DataModelPropertyAttribute attribute) + { + if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild)); + if (key == null) throw new ArgumentNullException(nameof(key)); + if (attribute == null) throw new ArgumentNullException(nameof(attribute)); + if (key.Contains('.')) + throw new ArtemisCoreException("The provided key contains an illegal character (.)"); + if (_dynamicChildren.ContainsKey(key)) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); + } + + if (GetType().GetProperty(key) != null) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use by a static property on this data model."); + } + + // Make sure a name is on the attribute or funny things might happen + attribute.Name ??= key.Humanize(); + if (dynamicChild is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } + + _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; + } + + /// + /// Removes a dynamic child from the data model by its key + /// + /// The key of the dynamic child to remove public void RemoveDynamicChildByKey(string key) { - _dynamicDataModels.TryGetValue(key, out DataModel? childDataModel); - if (childDataModel == null) + if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) return; - _dynamicDataModels.Remove(key); - OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(childDataModel, key)); + _dynamicChildren.Remove(key); + OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild.Value, key)); } /// - /// Removes a dynamic data model from this data model + /// Removes a dynamic child from this data model /// - /// The dynamic data model to remove - public void RemoveDynamicChild(DataModel dynamicDataModel) + /// The dynamic data child to remove + public void RemoveDynamicChild(T dynamicChild) where T : class { - List keys = _dynamicDataModels.Where(kvp => kvp.Value == dynamicDataModel).Select(kvp => kvp.Key).ToList(); + List keys = _dynamicChildren.Where(kvp => kvp.Value.Value == dynamicChild).Select(kvp => kvp.Key).ToList(); foreach (string key in keys) { - _dynamicDataModels.Remove(key); - OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(dynamicDataModel, key)); + _dynamicChildren.Remove(key); + OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key)); } } /// - /// Removes all dynamic data models from this data model + /// Removes all dynamic children from this data model /// public void ClearDynamicChildren() { - while (_dynamicDataModels.Any()) - RemoveDynamicChildByKey(_dynamicDataModels.First().Key); + while (_dynamicChildren.Any()) + RemoveDynamicChildByKey(_dynamicChildren.First().Key); } /// - /// Gets a dynamic data model of type by its key + /// Gets a dynamic child of type by its key /// /// The type of data model you expect - /// The unique key of the dynamic data model - /// If found, the dynamic data model otherwise null - public T? DynamicChild(string key) where T : DataModel + /// The unique key of the dynamic child + /// If found, the dynamic child otherwise null + public T? DynamicChild(string key) { - _dynamicDataModels.TryGetValue(key, out DataModel? value); - return value as T; - } + if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) + return default; - #region Events - - /// - /// Occurs when a dynamic data model has been added to this data model - /// - public event EventHandler? DynamicDataModelAdded; - - /// - /// Occurs when a dynamic data model has been removed from this data model - /// - public event EventHandler? DynamicDataModelRemoved; - - /// - /// Invokes the event - /// - protected virtual void OnDynamicDataModelAdded(DynamicDataModelEventArgs e) - { - DynamicDataModelAdded?.Invoke(this, e); + if (dynamicChild.Value is not T) + return default; + return (T?) dynamicChild.Value; } /// - /// Invokes the event + /// Occurs when a dynamic child has been added to this data model /// - protected virtual void OnDynamicDataModelRemoved(DynamicDataModelEventArgs e) + public event EventHandler? DynamicChildAdded; + + /// + /// Occurs when a dynamic child has been removed from this data model + /// + public event EventHandler? DynamicChildRemoved; + + /// + /// Invokes the event + /// + protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e) { - DynamicDataModelRemoved?.Invoke(this, e); + DynamicChildAdded?.Invoke(this, e); } - #endregion + /// + /// Invokes the event + /// + protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e) + { + DynamicChildRemoved?.Invoke(this, e); + } } + + /// + /// Represents a record of a dynamic child value with its property attribute + /// + public record DynamicChild(DataModelPropertyAttribute Attribute, object? Value); } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 56bbbf5e7..78490468a 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -239,9 +239,10 @@ namespace Artemis.UI.Shared // Add missing dynamic children object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue(); if (value is DataModel dataModel) - foreach (KeyValuePair kvp in dataModel.DynamicDataModels) + { + foreach (var (key, dynamicChild) in dataModel.DynamicChildren) { - string childPath = AppendToPath(kvp.Key); + string childPath = AppendToPath(key); if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) continue; @@ -249,6 +250,7 @@ namespace Artemis.UI.Shared if (child != null) Children.Add(child); } + } // Remove dynamic children that have been removed from the data model List toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList(); From 6904a137e17c47e982a6efccd1bc17a820944b59 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 19 Apr 2021 17:43:46 +0200 Subject: [PATCH 2/5] Device visualizer - Fixed memory leak Device visualizer - Improved performance on very large devices Dynamic data model - Reworked API --- .../Events/DynamicDataModelChildEventArgs.cs | 4 +- .../Profile/DataModel/DataModelPathSegment.cs | 14 +- .../Plugins/DataModelExpansions/DataModel.cs | 362 +++++++++--------- .../DataModelExpansions/DynamicChild.cs | 66 ++++ src/Artemis.Core/Services/RgbService.cs | 2 +- .../Controls/DeviceVisualizer.cs | 10 +- .../Controls/DeviceVisualizerLed.cs | 15 +- 7 files changed, 274 insertions(+), 199 deletions(-) create mode 100644 src/Artemis.Core/Plugins/DataModelExpansions/DynamicChild.cs diff --git a/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs index 61e69c6d1..9248a716a 100644 --- a/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs +++ b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs @@ -8,7 +8,7 @@ namespace Artemis.Core /// public class DynamicDataModelChildEventArgs : EventArgs { - internal DynamicDataModelChildEventArgs(object? dynamicChild, string key) + internal DynamicDataModelChildEventArgs(DynamicChild dynamicChild, string key) { DynamicChild = dynamicChild; Key = key; @@ -17,7 +17,7 @@ namespace Artemis.Core /// /// Gets the dynamic data model child /// - public object? DynamicChild { get; } + public DynamicChild DynamicChild { get; } /// /// Gets the key of the dynamic data model on the parent diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index f78bc7c54..27f4294ed 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -15,8 +15,8 @@ namespace Artemis.Core { private Expression>? _accessorLambda; private DataModel? _dynamicDataModel; - private Type _dynamicDataModelType; - private DataModelPropertyAttribute _dynamicDataModelAttribute; + private Type? _dynamicDataModelType; + private DataModelPropertyAttribute? _dynamicDataModelAttribute; internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path) { @@ -184,8 +184,8 @@ namespace Artemis.Core // If a dynamic data model is found the use that bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild); - if (hasDynamicChild && dynamicChild?.Value != null) - DetermineDynamicType(dynamicChild.Value, dynamicChild.Attribute); + if (hasDynamicChild && dynamicChild?.BaseValue != null) + DetermineDynamicType(dynamicChild.BaseValue, dynamicChild.Attribute); _dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded; _dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved; @@ -212,12 +212,14 @@ namespace Artemis.Core accessorExpression = Expression.PropertyOrField(expression, Identifier); // A dynamic segment calls the generic method DataModel.DynamicChild and provides the identifier as an argument else + { accessorExpression = Expression.Call( expression, - nameof(DataModel.DynamicChild), + nameof(DataModel.GetDynamicChildValue), _dynamicDataModelType != null ? new[] { _dynamicDataModelType } : null, Expression.Constant(Identifier) ); + } _accessorLambda = Expression.Lambda>( // Wrap with a null check @@ -290,7 +292,7 @@ namespace Artemis.Core private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) { - if (e.DynamicChild == _dynamicDataModel) + if (e.DynamicChild.BaseValue == _dynamicDataModel) DataModelPath.Initialize(); } diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index 392995a4c..2013a9e25 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Artemis.Core.Modules; @@ -66,183 +67,6 @@ namespace Artemis.Core.DataModelExpansions return new List().AsReadOnly(); } - /// - /// Adds a dynamic child to this data model - /// - /// The dynamic child to add - /// The key of the child, must be unique to this data model - /// An optional human readable name, if not provided the key will be used in a humanized form - public T AddDynamicChild(T dynamicChild, string key, string? name = null) - { - if (dynamicChild == null) - throw new ArgumentNullException(nameof(dynamicChild)); - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (key.Contains('.')) - throw new ArtemisCoreException("The provided key contains an illegal character (.)"); - if (_dynamicChildren.ContainsKey(key)) - { - throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + - "because the key is already in use on by another dynamic property this data model."); - } - - if (GetType().GetProperty(key) != null) - { - throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + - "because the key is already in use by a static property on this data model."); - } - - DataModelPropertyAttribute attribute = new() - { - Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name - }; - if (dynamicChild is DataModel dynamicDataModel) - { - dynamicDataModel.Feature = Feature; - dynamicDataModel.DataModelDescription = attribute; - } - - _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); - - OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); - return dynamicChild; - } - - /// - /// Adds a dynamic child to this data model - /// - /// The dynamic child to add - /// The key of the child, must be unique to this data model - /// A human readable for your dynamic child, shown in the UI - /// An optional description, shown in the UI - public T AddDynamicChild(T dynamicChild, string key, string name, string description) - { - if (dynamicChild == null) - throw new ArgumentNullException(nameof(dynamicChild)); - if (key == null) - throw new ArgumentNullException(nameof(key)); - if (key.Contains('.')) - throw new ArtemisCoreException("The provided key contains an illegal character (.)"); - if (_dynamicChildren.ContainsKey(key)) - { - throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + - "because the key is already in use on by another dynamic property this data model."); - } - - if (GetType().GetProperty(key) != null) - { - throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + - "because the key is already in use by a static property on this data model."); - } - - DataModelPropertyAttribute attribute = new() - { - Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name, - Description = description - }; - if (dynamicChild is DataModel dynamicDataModel) - { - dynamicDataModel.Feature = Feature; - dynamicDataModel.DataModelDescription = attribute; - } - - _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); - - OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); - return dynamicChild; - } - - /// - /// Adds a dynamic child to this data model - /// - /// The dynamic child to add - /// The key of the child, must be unique to this data model - /// A data model property attribute describing the dynamic child - public T AddDynamicChild(T dynamicChild, string key, DataModelPropertyAttribute attribute) - { - if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild)); - if (key == null) throw new ArgumentNullException(nameof(key)); - if (attribute == null) throw new ArgumentNullException(nameof(attribute)); - if (key.Contains('.')) - throw new ArtemisCoreException("The provided key contains an illegal character (.)"); - if (_dynamicChildren.ContainsKey(key)) - { - throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + - "because the key is already in use on by another dynamic property this data model."); - } - - if (GetType().GetProperty(key) != null) - { - throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + - "because the key is already in use by a static property on this data model."); - } - - // Make sure a name is on the attribute or funny things might happen - attribute.Name ??= key.Humanize(); - if (dynamicChild is DataModel dynamicDataModel) - { - dynamicDataModel.Feature = Feature; - dynamicDataModel.DataModelDescription = attribute; - } - - _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); - - OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); - return dynamicChild; - } - - /// - /// Removes a dynamic child from the data model by its key - /// - /// The key of the dynamic child to remove - public void RemoveDynamicChildByKey(string key) - { - if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) - return; - - _dynamicChildren.Remove(key); - OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild.Value, key)); - } - - /// - /// Removes a dynamic child from this data model - /// - /// The dynamic data child to remove - public void RemoveDynamicChild(T dynamicChild) where T : class - { - List keys = _dynamicChildren.Where(kvp => kvp.Value.Value == dynamicChild).Select(kvp => kvp.Key).ToList(); - foreach (string key in keys) - { - _dynamicChildren.Remove(key); - OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key)); - } - } - - /// - /// Removes all dynamic children from this data model - /// - public void ClearDynamicChildren() - { - while (_dynamicChildren.Any()) - RemoveDynamicChildByKey(_dynamicChildren.First().Key); - } - - /// - /// Gets a dynamic child of type by its key - /// - /// The type of data model you expect - /// The unique key of the dynamic child - /// If found, the dynamic child otherwise null - public T? DynamicChild(string key) - { - if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) - return default; - - if (dynamicChild.Value is not T) - return default; - return (T?) dynamicChild.Value; - } - /// /// Occurs when a dynamic child has been added to this data model /// @@ -268,10 +92,184 @@ namespace Artemis.Core.DataModelExpansions { DynamicChildRemoved?.Invoke(this, e); } - } - /// - /// Represents a record of a dynamic child value with its property attribute - /// - public record DynamicChild(DataModelPropertyAttribute Attribute, object? Value); + #region Dynamic children + + /// + /// Adds a dynamic child to this data model + /// + /// The key of the child, must be unique to this data model + /// The initial value of the dynamic child + /// The resulting dynamic child which can be used to further update the value + public DynamicChild AddDynamicChild(string key, T initialValue) + { + return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute()); + } + + /// + /// Adds a dynamic child to this data model + /// + /// The key of the child, must be unique to this data model + /// The initial value of the dynamic child + /// A human readable name for your dynamic child, shown in the UI + /// An optional description, shown in the UI + /// The resulting dynamic child which can be used to further update the value + public DynamicChild AddDynamicChild(string key, T initialValue, string name, string? description = null) + { + return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute {Name = name, Description = description}); + } + + /// + /// Adds a dynamic child to this data model + /// + /// The key of the child, must be unique to this data model + /// The initial value of the dynamic child + /// A data model property attribute describing the dynamic child + /// The resulting dynamic child which can be used to further update the value + public DynamicChild AddDynamicChild(string key, T initialValue, DataModelPropertyAttribute attribute) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (initialValue == null) throw new ArgumentNullException(nameof(initialValue)); + if (attribute == null) throw new ArgumentNullException(nameof(attribute)); + if (key.Contains('.')) + throw new ArtemisCoreException("The provided key contains an illegal character (.)"); + if (_dynamicChildren.ContainsKey(key)) + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); + + if (GetType().GetProperty(key) != null) + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use by a static property on this data model."); + + // Make sure a name is on the attribute or funny things might happen + attribute.Name ??= key.Humanize(); + if (initialValue is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } + + DynamicChild dynamicChild = new(initialValue, key, attribute); + _dynamicChildren.Add(key, dynamicChild); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; + } + + /// + /// Gets a previously added dynamic child by its key + /// + /// The key of the dynamic child + public DynamicChild GetDynamicChild(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + return DynamicChildren[key]; + } + + /// + /// Gets a previously added dynamic child by its key + /// + /// The typer of dynamic child you are expecting + /// The key of the dynamic child + /// + public DynamicChild GetDynamicChild(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + return (DynamicChild) DynamicChildren[key]; + } + + /// + /// Gets a previously added dynamic child by its key + /// + /// The key of the dynamic child + /// + /// When this method returns, the associated with the specified key, + /// if the key is found; otherwise, . This parameter is passed uninitialized. + /// + /// + /// if the data model contains the dynamic child; otherwise + /// + public bool TryGetDynamicChild(string key, [MaybeNullWhen(false)] out DynamicChild dynamicChild) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + + dynamicChild = null; + if (!DynamicChildren.TryGetValue(key, out DynamicChild? value)) + return false; + + dynamicChild = value; + return true; + } + + /// + /// Gets a previously added dynamic child by its key + /// + /// The typer of dynamic child you are expecting + /// The key of the dynamic child + /// + /// When this method returns, the associated with the specified + /// key, if the key is found; otherwise, . This parameter is passed uninitialized. + /// + /// + /// if the data model contains the dynamic child; otherwise + /// + public bool TryGetDynamicChild(string key, [MaybeNullWhen(false)] out DynamicChild dynamicChild) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + + dynamicChild = null; + if (!DynamicChildren.TryGetValue(key, out DynamicChild? value)) + return false; + if (value is not DynamicChild typedDynamicChild) + return false; + dynamicChild = typedDynamicChild; + return true; + } + + /// + /// Removes a dynamic child from the data model by its key + /// + /// The key of the dynamic child to remove + public void RemoveDynamicChildByKey(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) + return; + + _dynamicChildren.Remove(key); + OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key)); + } + + /// + /// Removes a dynamic child from this data model + /// + /// The dynamic data child to remove + public void RemoveDynamicChild(DynamicChild dynamicChild) + { + if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild)); + List keys = _dynamicChildren.Where(kvp => kvp.Value.BaseValue == dynamicChild).Select(kvp => kvp.Key).ToList(); + foreach (string key in keys) + { + _dynamicChildren.Remove(key); + OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key)); + } + } + + /// + /// Removes all dynamic children from this data model + /// + public void ClearDynamicChildren() + { + while (_dynamicChildren.Any()) + RemoveDynamicChildByKey(_dynamicChildren.First().Key); + } + + // Used a runtime by data model paths only + internal T? GetDynamicChildValue(string key) + { + return TryGetDynamicChild(key, out DynamicChild? dynamicChild) ? dynamicChild.Value : default; + } + + #endregion + } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DynamicChild.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DynamicChild.cs new file mode 100644 index 000000000..5e5732b55 --- /dev/null +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DynamicChild.cs @@ -0,0 +1,66 @@ +using System; + +namespace Artemis.Core.DataModelExpansions +{ + /// + /// Represents a dynamic child value with its property attribute + /// + public class DynamicChild : DynamicChild + { + internal DynamicChild(T value, string key, DataModelPropertyAttribute attribute) : base(key, attribute, typeof(T)) + { + Value = value; + } + + /// + /// Gets or sets the current value of the dynamic child + /// + public new T Value { get; set; } + + /// + protected override object? GetValue() + { + return Value; + } + } + + /// + /// Represents a dynamic child value with its property attribute + /// + public abstract class DynamicChild + { + internal DynamicChild(string key, DataModelPropertyAttribute attribute, Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + Key = key ?? throw new ArgumentNullException(nameof(key)); + Attribute = attribute ?? throw new ArgumentNullException(nameof(attribute)); + Type = type; + } + + /// + /// Gets the key of the dynamic child + /// + public string Key { get; } + + /// + /// Gets the attribute describing the dynamic child + /// + public DataModelPropertyAttribute Attribute { get; } + + /// + /// Gets the type of + /// + public Type Type { get; } + + /// + /// Gets the current value of the dynamic child + /// + public object? BaseValue => GetValue(); + + /// + /// Gets the current value of the dynamic child + /// + /// The current value of the dynamic child + protected abstract object? GetValue(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 5e4a5066a..26c66ed54 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -113,7 +113,7 @@ namespace Artemis.Core.Services private void SurfaceOnException(ExceptionEventArgs args) { - _logger.Warning("Surface threw e"); + _logger.Warning(args.Exception, "Surface caught exception"); throw args.Exception; } diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index fb3b77506..e3fd07f40 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -314,6 +314,7 @@ namespace Artemis.UI.Shared ImageBrush bitmapBrush = new(bitmap); bitmapBrush.Freeze(); _backingStore.OpacityMask = bitmapBrush; + _backingStore.Children.Clear(); InvalidateMeasure(); } @@ -331,14 +332,19 @@ namespace Artemis.UI.Shared private void Render() { DrawingContext drawingContext = _backingStore.Append(); - // DrawingContext drawingContext = _backingStore.Open(); if (HighlightedLeds != null && HighlightedLeds.Any()) + { + // Faster on large devices, maybe a bit slower on smaller ones but that's ok + ILookup toHighlight = HighlightedLeds.ToLookup(l => l); foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds) - deviceVisualizerLed.RenderColor(_backingStore, drawingContext, !HighlightedLeds.Contains(deviceVisualizerLed.Led)); + deviceVisualizerLed.RenderColor(_backingStore, drawingContext, !toHighlight.Contains(deviceVisualizerLed.Led)); + } else + { foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds) deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false); + } drawingContext.Close(); } diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs index 45dc4ac41..cf0699306 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -14,10 +14,10 @@ namespace Artemis.UI.Shared { private const byte Dimmed = 100; private const byte NonDimmed = 255; - private GeometryDrawing? _geometryDrawing; private Color _renderColor; - + private GeometryDrawing? _geometryDrawing; private SolidColorBrush? _renderColorBrush; + private DrawingGroup? _lastBackingStore; public DeviceVisualizerLed(ArtemisLed led) { @@ -43,12 +43,12 @@ namespace Artemis.UI.Shared public void RenderColor(DrawingGroup backingStore, DrawingContext drawingContext, bool isDimmed) { - if (DisplayGeometry == null) + if (DisplayGeometry == null || backingStore == null) return; _renderColorBrush ??= new SolidColorBrush(); _geometryDrawing ??= new GeometryDrawing(_renderColorBrush, null, new RectangleGeometry(LedRect)); - + byte r = Led.RgbLed.Color.GetR(); byte g = Led.RgbLed.Color.GetG(); byte b = Led.RgbLed.Color.GetB(); @@ -59,9 +59,12 @@ namespace Artemis.UI.Shared _renderColor.B = b; _renderColorBrush.Color = _renderColor; - - if (!backingStore.Children.Contains(_geometryDrawing)) + if (_lastBackingStore != backingStore) + { backingStore.Children.Add(_geometryDrawing); + _lastBackingStore = backingStore; + } + } public void RenderImage(DrawingContext drawingContext) From f3a823644eb4a95ede2f56c6289d0fd103be2cd6 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 20 Apr 2021 00:29:48 +0200 Subject: [PATCH 3/5] Dynamic data models - Fixed data model paths not handling inheritance very well --- .../Plugins/DataModelExpansions/DataModel.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index 2013a9e25..57f2c8b7e 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -134,12 +134,16 @@ namespace Artemis.Core.DataModelExpansions if (key.Contains('.')) throw new ArtemisCoreException("The provided key contains an illegal character (.)"); if (_dynamicChildren.ContainsKey(key)) + { throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + "because the key is already in use on by another dynamic property this data model."); + } if (GetType().GetProperty(key) != null) + { throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + "because the key is already in use by a static property on this data model."); + } // Make sure a name is on the attribute or funny things might happen attribute.Name ??= key.Humanize(); @@ -155,7 +159,7 @@ namespace Artemis.Core.DataModelExpansions OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); return dynamicChild; } - + /// /// Gets a previously added dynamic child by its key /// @@ -208,7 +212,8 @@ namespace Artemis.Core.DataModelExpansions /// The key of the dynamic child /// /// When this method returns, the associated with the specified - /// key, if the key is found; otherwise, . This parameter is passed uninitialized. + /// key, if the key is found and the type matches; otherwise, . This parameter is passed + /// uninitialized. /// /// /// if the data model contains the dynamic child; otherwise @@ -267,7 +272,9 @@ namespace Artemis.Core.DataModelExpansions // Used a runtime by data model paths only internal T? GetDynamicChildValue(string key) { - return TryGetDynamicChild(key, out DynamicChild? dynamicChild) ? dynamicChild.Value : default; + if (TryGetDynamicChild(key, out DynamicChild? dynamicChild) && dynamicChild.BaseValue != null) + return (T) dynamicChild.BaseValue; + return default; } #endregion From 2d02dba140a91f215522a352f92331195b5b2c04 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 20 Apr 2021 23:59:10 +0200 Subject: [PATCH 4/5] Profile rendering - Don't leave filter quality on undefined, set to low --- src/Artemis.Core/Models/Profile/Folder.cs | 2 +- src/Artemis.Core/Models/Profile/Layer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index f240a61c6..16231eed8 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -195,7 +195,7 @@ namespace Artemis.Core baseLayerEffect.Update(Timeline.Delta.TotalSeconds); } - SKPaint layerPaint = new(); + SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low}; try { SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 4ecf6298c..c7a17bc46 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -364,7 +364,7 @@ namespace Artemis.Core if (LayerBrush?.BrushType != LayerBrushType.Regular) return; - SKPaint layerPaint = new(); + SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low}; try { canvas.Save(); From 1a829034491064e2d84355e713bd596f5992d190 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 21 Apr 2021 17:25:55 +0200 Subject: [PATCH 5/5] Data model - Fixed prefix positioning Data model - Fixed prefix and affix not working on SKColors --- .../Display/DefaultDataModelDisplayView.xaml | 10 ++- .../Display/SKColorDataModelDisplayView.xaml | 63 +++++++++++++------ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml index a190a550a..b722b7d59 100644 --- a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml +++ b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml @@ -14,16 +14,19 @@ - - + + + - + + + @@ -15,25 +16,49 @@ + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file