mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Dynamic data models - Finished abstraction of paths
This commit is contained in:
parent
3d92c9185f
commit
26db794467
@ -11,75 +11,80 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public class DataModelPath
|
||||
{
|
||||
private readonly List<DataModelPathPart> _parts;
|
||||
private readonly LinkedList<DataModelPathPart> _parts;
|
||||
|
||||
// TODO: Make internal
|
||||
public DataModelPath(DataModel dataModel, string path)
|
||||
internal DataModelPath(DataModel dataModel, string path)
|
||||
{
|
||||
if (dataModel == null)
|
||||
throw new ArgumentNullException(nameof(dataModel));
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
|
||||
_parts = new List<DataModelPathPart>();
|
||||
|
||||
DataModel = dataModel;
|
||||
DataModel = dataModel ?? throw new ArgumentNullException(nameof(dataModel));
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
DataModelGuid = dataModel.PluginInfo.Guid;
|
||||
|
||||
_parts = new LinkedList<DataModelPathPart>();
|
||||
Initialize(path);
|
||||
}
|
||||
|
||||
private void Initialize(string path)
|
||||
{
|
||||
var parts = path.Split(".");
|
||||
foreach (var identifier in parts)
|
||||
_parts.Add(new DataModelPathPart(this, identifier));
|
||||
|
||||
var parameter = Expression.Parameter(typeof(DataModel), "dm");
|
||||
Expression expression = Expression.Convert(parameter, DataModel.GetType());
|
||||
Expression nullCondition = null;
|
||||
DataModelPathPart previous = null;
|
||||
|
||||
foreach (var part in _parts)
|
||||
{
|
||||
var notNull = Expression.NotEqual(expression, Expression.Constant(null));
|
||||
nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
|
||||
expression = part.Initialize(previous, parameter, expression, nullCondition);
|
||||
if (expression == null)
|
||||
return;
|
||||
|
||||
previous = part;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model at which this path starts
|
||||
/// </summary>
|
||||
public DataModel DataModel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string representation of the <see cref="DataModelPath" />
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether all <see cref="Parts" /> are valid
|
||||
/// </summary>
|
||||
public bool IsValid => Parts.All(p => p.Type != DataModelPathPartType.Invalid);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all parts of this path
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<DataModelPathPart> Parts => _parts.ToList().AsReadOnly();
|
||||
|
||||
internal Func<DataModel, object> Accessor { get; private set; }
|
||||
|
||||
internal Guid DataModelGuid { get; set; }
|
||||
|
||||
public string GetPathToPart(DataModelPathPart part)
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var endIndex = _parts.IndexOf(part);
|
||||
return endIndex < 0 ? null : string.Join('.', _parts.Take(endIndex + 1));
|
||||
return Path;
|
||||
}
|
||||
|
||||
internal DataModelPathPart GetPartBefore(DataModelPathPart dataModelPathPart)
|
||||
private void Initialize(string path)
|
||||
{
|
||||
var index = _parts.IndexOf(dataModelPathPart);
|
||||
return index > 0 ? _parts[index - 1] : null;
|
||||
}
|
||||
var parts = path.Split(".");
|
||||
for (var index = 0; index < parts.Length; index++)
|
||||
{
|
||||
var identifier = parts[index];
|
||||
var node = _parts.AddLast(new DataModelPathPart(this, identifier, string.Join('.', parts.Take(index + 1))));
|
||||
node.Value.Node = node;
|
||||
}
|
||||
|
||||
internal DataModelPathPart GetPartAfter(DataModelPathPart dataModelPathPart)
|
||||
{
|
||||
var index = _parts.IndexOf(dataModelPathPart);
|
||||
return index < _parts.Count - 1 ? _parts[index + 1] : null;
|
||||
var parameter = Expression.Parameter(typeof(DataModel), "dm");
|
||||
Expression expression = Expression.Convert(parameter, DataModel.GetType());
|
||||
Expression nullCondition = null;
|
||||
|
||||
foreach (var part in _parts)
|
||||
{
|
||||
var notNull = Expression.NotEqual(expression, Expression.Constant(null));
|
||||
nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
|
||||
expression = part.Initialize(parameter, expression, nullCondition);
|
||||
if (expression == null)
|
||||
return;
|
||||
}
|
||||
|
||||
Accessor = Expression.Lambda<Func<DataModel, object>>(
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
nullCondition,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))
|
||||
),
|
||||
parameter
|
||||
).Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
@ -10,10 +11,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public class DataModelPathPart
|
||||
{
|
||||
internal DataModelPathPart(DataModelPath dataModelPath, string identifier)
|
||||
internal DataModelPathPart(DataModelPath dataModelPath, string identifier, string path)
|
||||
{
|
||||
DataModelPath = dataModelPath;
|
||||
Identifier = identifier;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -26,6 +28,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path that leads to this part
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of data model this part of the path points to
|
||||
/// </summary>
|
||||
@ -40,14 +47,15 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets the previous part in the path
|
||||
/// </summary>
|
||||
public DataModelPathPart Previous => DataModelPath.GetPartBefore(this);
|
||||
public DataModelPathPart Previous => Node.Previous?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next part in the path
|
||||
/// </summary>
|
||||
public DataModelPathPart Next => DataModelPath.GetPartAfter(this);
|
||||
public DataModelPathPart Next => Node.Next?.Value;
|
||||
|
||||
internal Func<DataModel, object> Accessor { get; set; }
|
||||
internal LinkedListNode<DataModelPathPart> Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current value of the path up to this part
|
||||
@ -58,6 +66,39 @@ namespace Artemis.Core
|
||||
return Type == DataModelPathPartType.Invalid ? null : Accessor(DataModelPath.DataModel);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Type}] {Path}";
|
||||
}
|
||||
|
||||
internal Expression Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
var previousValue = Previous != null ? Previous.GetValue() : DataModelPath.DataModel;
|
||||
if (previousValue == null)
|
||||
{
|
||||
Type = DataModelPathPartType.Invalid;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine this part's type by looking for a dynamic data model with the identifier
|
||||
if (previousValue is DataModel dataModel)
|
||||
{
|
||||
var hasDynamicDataModel = dataModel.DynamicDataModels.TryGetValue(Identifier, out var dynamicDataModel);
|
||||
// If a dynamic data model is found the use that
|
||||
if (hasDynamicDataModel)
|
||||
DetermineDynamicType(dynamicDataModel);
|
||||
// Otherwise look for a static type
|
||||
else
|
||||
DetermineStaticType(previousValue);
|
||||
}
|
||||
// Only data models can have dynamic types so if it is something else, its always going to be static
|
||||
else
|
||||
DetermineStaticType(previousValue);
|
||||
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
private Expression CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
if (Type == DataModelPathPartType.Invalid)
|
||||
@ -81,45 +122,19 @@ namespace Artemis.Core
|
||||
);
|
||||
}
|
||||
|
||||
var test = Expression.Lambda<Func<DataModel, object>>(
|
||||
Expression.Condition(nullCondition, expression, Expression.Default(expression.Type)), // Wrap with a null check
|
||||
parameter
|
||||
);
|
||||
Accessor = Expression.Lambda<Func<DataModel, object>>(
|
||||
Expression.Condition(nullCondition, expression, Expression.Default(expression.Type)), // Wrap with a null check
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
nullCondition,
|
||||
Expression.Convert(accessorExpression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(accessorExpression.Type), typeof(object))
|
||||
),
|
||||
parameter
|
||||
).Compile();
|
||||
|
||||
return accessorExpression;
|
||||
}
|
||||
|
||||
internal Expression Initialize(DataModelPathPart previous, ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
var previousValue = previous != null ? previous.GetValue() : DataModelPath.DataModel;
|
||||
if (previousValue == null)
|
||||
{
|
||||
Type = DataModelPathPartType.Invalid;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine this part's type by looking for a dynamic data model with the identifier
|
||||
if (previousValue is DataModel dataModel)
|
||||
{
|
||||
var hasDynamicDataModel = dataModel.DynamicDataModels.TryGetValue(Identifier, out var dynamicDataModel);
|
||||
// If a dynamic data model is found the use that
|
||||
if (hasDynamicDataModel)
|
||||
DetermineDynamicType(dynamicDataModel);
|
||||
// Otherwise look for a static type
|
||||
else
|
||||
DetermineStaticType(previousValue);
|
||||
}
|
||||
// Only data models can have dynamic types so if it is something else, its always going to be static
|
||||
else
|
||||
DetermineStaticType(previousValue);
|
||||
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
private void DetermineDynamicType(DataModel dynamicDataModel)
|
||||
{
|
||||
Type = DataModelPathPartType.Dynamic;
|
||||
|
||||
@ -79,7 +79,7 @@ namespace Artemis.Core.DataModelExpansions
|
||||
}
|
||||
|
||||
dynamicDataModel.PluginInfo = PluginInfo;
|
||||
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute()
|
||||
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute
|
||||
{
|
||||
Name = name ?? key.Humanize(),
|
||||
Description = description
|
||||
@ -189,5 +189,29 @@ namespace Artemis.Core.DataModelExpansions
|
||||
var child = GetTypeAtPath(path);
|
||||
return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null;
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a dynamic data model has been added to this data model
|
||||
/// </summary>
|
||||
public event EventHandler DynamicDataBindingAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a dynamic data model has been removed from this data model
|
||||
/// </summary>
|
||||
public event EventHandler DynamicDataBindingRemoved;
|
||||
|
||||
protected virtual void OnDynamicDataBindingAdded()
|
||||
{
|
||||
DynamicDataBindingAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnDynamicDataBindingRemoved()
|
||||
{
|
||||
DynamicDataBindingRemoved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ namespace Artemis.Plugins.DataModelExpansions.TestData.DataModels
|
||||
{
|
||||
public PluginDataModel()
|
||||
{
|
||||
// PluginSubDataModel = new PluginSubDataModel();
|
||||
PluginSubDataModel = new PluginSubDataModel();
|
||||
ListItems = new List<SomeListItem>();
|
||||
for (var i = 0; i < 20; i++)
|
||||
ListItems.Add(new SomeListItem {ItemName = $"Item {i + 1}", Number = i});
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Plugins.DataModelExpansions.TestData.DataModels;
|
||||
using SkiaSharp;
|
||||
@ -16,11 +15,6 @@ namespace Artemis.Plugins.DataModelExpansions.TestData
|
||||
AddTimedUpdate(TimeSpan.FromSeconds(1), TimedUpdate);
|
||||
|
||||
DataModel.AddDynamicChild(new DynamicDataModel(), "Dynamic1", "Dynamic data model 1");
|
||||
|
||||
// var testPath1 = new DataModelPath(DataModel, "TemplateDataModelString");
|
||||
var testPath2 = new DataModelPath(DataModel, "PluginSubDataModel.Number");
|
||||
// var testPath3 = new DataModelPath(DataModel, "Dynamic1.DynamicString");
|
||||
// var testPath4 = new DataModelPath(DataModel, "TemplateDataModelString");
|
||||
}
|
||||
|
||||
public override void DisablePlugin()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user