Device visualizer - Fixed memory leak

Device visualizer - Improved performance on very large devices
Dynamic data model - Reworked API
This commit is contained in:
Robert committed 2021-04-19 17:43:46 +02:00
1 parent 8cba36f5d4
commit 6904a137e1
7 files changed
+274 -199

No files matched your search

@@ -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<PropertyInfo>().AsReadOnly();
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="dynamicChild">The dynamic child to add</param>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="name">An optional human readable name, if not provided the key will be used in a humanized form</param>
public T AddDynamicChild<T>(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;
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="dynamicChild">The dynamic child to add</param>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="name">A human readable for your dynamic child, shown in the UI</param>
/// <param name="description">An optional description, shown in the UI</param>
public T AddDynamicChild<T>(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;
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="dynamicChild">The dynamic child to add</param>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
public T AddDynamicChild<T>(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;
}
/// <summary>
/// Removes a dynamic child from the data model by its key
/// </summary>
/// <param name="key">The key of the dynamic child to remove</param>
public void RemoveDynamicChildByKey(string key)
{
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return;
_dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild.Value, key));
}
/// <summary>
/// Removes a dynamic child from this data model
/// </summary>
/// <param name="dynamicChild">The dynamic data child to remove</param>
public void RemoveDynamicChild<T>(T dynamicChild) where T : class
{
List<string> 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));
}
}
/// <summary>
/// Removes all dynamic children from this data model
/// </summary>
public void ClearDynamicChildren()
{
while (_dynamicChildren.Any())
RemoveDynamicChildByKey(_dynamicChildren.First().Key);
}
/// <summary>
/// Gets a dynamic child of type <typeparamref name="T" /> by its key
/// </summary>
/// <typeparam name="T">The type of data model you expect</typeparam>
/// <param name="key">The unique key of the dynamic child</param>
/// <returns>If found, the dynamic child otherwise <c>null</c></returns>
public T? DynamicChild<T>(string key)
{
if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return default;
if (dynamicChild.Value is not T)
return default;
return (T?) dynamicChild.Value;
}
/// <summary>
/// Occurs when a dynamic child has been added to this data model
/// </summary>
@@ -268,10 +92,184 @@ namespace Artemis.Core.DataModelExpansions
{
DynamicChildRemoved?.Invoke(this, e);
}
}
/// <summary>
/// Represents a record of a dynamic child value with its property attribute
/// </summary>
public record DynamicChild(DataModelPropertyAttribute Attribute, object? Value);
#region Dynamic children
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute());
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="name">A human readable name for your dynamic child, shown in the UI</param>
/// <param name="description">An optional description, shown in the UI</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, string name, string? description = null)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute {Name = name, Description = description});
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(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<T> dynamicChild = new(initialValue, key, attribute);
_dynamicChildren.Add(key, dynamicChild);
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
public DynamicChild GetDynamicChild(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return DynamicChildren[key];
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <returns></returns>
public DynamicChild<T> GetDynamicChild<T>(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return (DynamicChild<T>) DynamicChildren[key];
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild" /> associated with the specified key,
/// if the key is found; otherwise, <see langword="null" />. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
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;
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild{T}" /> associated with the specified
/// key, if the key is found; otherwise, <see langword="null" />. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild<T>(string key, [MaybeNullWhen(false)] out DynamicChild<T> 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<T> typedDynamicChild)
return false;
dynamicChild = typedDynamicChild;
return true;
}
/// <summary>
/// Removes a dynamic child from the data model by its key
/// </summary>
/// <param name="key">The key of the dynamic child to remove</param>
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));
}
/// <summary>
/// Removes a dynamic child from this data model
/// </summary>
/// <param name="dynamicChild">The dynamic data child to remove</param>
public void RemoveDynamicChild(DynamicChild dynamicChild)
{
if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild));
List<string> 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));
}
}
/// <summary>
/// Removes all dynamic children from this data model
/// </summary>
public void ClearDynamicChildren()
{
while (_dynamicChildren.Any())
RemoveDynamicChildByKey(_dynamicChildren.First().Key);
}
// Used a runtime by data model paths only
internal T? GetDynamicChildValue<T>(string key)
{
return TryGetDynamicChild(key, out DynamicChild<T>? dynamicChild) ? dynamicChild.Value : default;
}
#endregion
}
}
@@ -0,0 +1,66 @@
using System;
namespace Artemis.Core.DataModelExpansions
{
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
public class DynamicChild<T> : DynamicChild
{
internal DynamicChild(T value, string key, DataModelPropertyAttribute attribute) : base(key, attribute, typeof(T))
{
Value = value;
}
/// <summary>
/// Gets or sets the current value of the dynamic child
/// </summary>
public new T Value { get; set; }
/// <inheritdoc />
protected override object? GetValue()
{
return Value;
}
}
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
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;
}
/// <summary>
/// Gets the key of the dynamic child
/// </summary>
public string Key { get; }
/// <summary>
/// Gets the attribute describing the dynamic child
/// </summary>
public DataModelPropertyAttribute Attribute { get; }
/// <summary>
/// Gets the type of <see cref="BaseValue" />
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
public object? BaseValue => GetValue();
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
/// <returns>The current value of the dynamic child</returns>
protected abstract object? GetValue();
}
}