mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Dynamic data models - WIP
This commit is contained in:
parent
b7b012f863
commit
3d92c9185f
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using Artemis.Core.DataModelExpansions;
|
using Artemis.Core.DataModelExpansions;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
@ -10,17 +11,51 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataModelPath
|
public class DataModelPath
|
||||||
{
|
{
|
||||||
private readonly LinkedList<DataModelPathPart> _parts;
|
private readonly List<DataModelPathPart> _parts;
|
||||||
|
|
||||||
internal DataModelPath()
|
// TODO: Make internal
|
||||||
|
public DataModelPath(DataModel dataModel, string path)
|
||||||
{
|
{
|
||||||
_parts = new LinkedList<DataModelPathPart>();
|
if (dataModel == null)
|
||||||
|
throw new ArgumentNullException(nameof(dataModel));
|
||||||
|
if (path == null)
|
||||||
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
|
||||||
|
_parts = new List<DataModelPathPart>();
|
||||||
|
|
||||||
|
DataModel = dataModel;
|
||||||
|
DataModelGuid = dataModel.PluginInfo.Guid;
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Gets the data model at which this path starts
|
/// Gets the data model at which this path starts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataModel DataModel { get; private set; }
|
public DataModel DataModel { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read-only list of all parts of this path
|
/// Gets a read-only list of all parts of this path
|
||||||
@ -28,5 +63,23 @@ namespace Artemis.Core
|
|||||||
public IReadOnlyCollection<DataModelPathPart> Parts => _parts.ToList().AsReadOnly();
|
public IReadOnlyCollection<DataModelPathPart> Parts => _parts.ToList().AsReadOnly();
|
||||||
|
|
||||||
internal Guid DataModelGuid { get; set; }
|
internal Guid DataModelGuid { get; set; }
|
||||||
|
|
||||||
|
public string GetPathToPart(DataModelPathPart part)
|
||||||
|
{
|
||||||
|
var endIndex = _parts.IndexOf(part);
|
||||||
|
return endIndex < 0 ? null : string.Join('.', _parts.Take(endIndex + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DataModelPathPart GetPartBefore(DataModelPathPart dataModelPathPart)
|
||||||
|
{
|
||||||
|
var index = _parts.IndexOf(dataModelPathPart);
|
||||||
|
return index > 0 ? _parts[index - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DataModelPathPart GetPartAfter(DataModelPathPart dataModelPathPart)
|
||||||
|
{
|
||||||
|
var index = _parts.IndexOf(dataModelPathPart);
|
||||||
|
return index < _parts.Count - 1 ? _parts[index + 1] : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,18 +1,136 @@
|
|||||||
namespace Artemis.Core
|
using System;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using Artemis.Core.DataModelExpansions;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a part of a data model path
|
/// Represents a part of a data model path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataModelPathPart
|
public class DataModelPathPart
|
||||||
{
|
{
|
||||||
|
internal DataModelPathPart(DataModelPath dataModelPath, string identifier)
|
||||||
|
{
|
||||||
|
DataModelPath = dataModelPath;
|
||||||
|
Identifier = identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data model path this is a part of
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPath DataModelPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the identifier that is associated with this part
|
/// Gets the identifier that is associated with this part
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Identifier { get; private set; }
|
public string Identifier { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of data model this part of the path points to
|
/// Gets the type of data model this part of the path points to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataModelPathPartType Type { get; private set; }
|
public DataModelPathPartType Type { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of dynamic data model this path points to
|
||||||
|
/// <para>Not used if the <see cref="Type" /> is <see cref="DataModelPathPartType.Static" /></para>
|
||||||
|
/// </summary>
|
||||||
|
public Type DynamicDataModelType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the previous part in the path
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPathPart Previous => DataModelPath.GetPartBefore(this);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next part in the path
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPathPart Next => DataModelPath.GetPartAfter(this);
|
||||||
|
|
||||||
|
internal Func<DataModel, object> Accessor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the current value of the path up to this part
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object GetValue()
|
||||||
|
{
|
||||||
|
return Type == DataModelPathPartType.Invalid ? null : Accessor(DataModelPath.DataModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||||
|
{
|
||||||
|
if (Type == DataModelPathPartType.Invalid)
|
||||||
|
{
|
||||||
|
Accessor = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression accessorExpression;
|
||||||
|
// A static part just needs to access the property or filed
|
||||||
|
if (Type == DataModelPathPartType.Static)
|
||||||
|
accessorExpression = Expression.PropertyOrField(expression, Identifier);
|
||||||
|
// A dynamic part calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accessorExpression = Expression.Call(
|
||||||
|
expression,
|
||||||
|
nameof(DataModel.DynamicChild),
|
||||||
|
new[] {DynamicDataModelType},
|
||||||
|
Expression.Constant(Identifier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
DynamicDataModelType = dynamicDataModel.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetermineStaticType(object previous)
|
||||||
|
{
|
||||||
|
var previousType = previous.GetType();
|
||||||
|
var property = previousType.GetProperty(Identifier, BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
Type = property == null ? DataModelPathPartType.Invalid : DataModelPathPartType.Static;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,6 +5,11 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum DataModelPathPartType
|
public enum DataModelPathPartType
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an invalid data model type that points to a missing data model
|
||||||
|
/// </summary>
|
||||||
|
Invalid,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a static data model type that points to a data model defined in code
|
/// Represents a static data model type that points to a data model defined in code
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
namespace Artemis.Core.DataModelExpansions
|
namespace Artemis.Core.DataModelExpansions
|
||||||
{
|
{
|
||||||
@ -59,6 +60,11 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
/// <param name="description">An optional description</param>
|
/// <param name="description">An optional description</param>
|
||||||
public void AddDynamicChild(DataModel dynamicDataModel, string key, string name = null, string description = null)
|
public void AddDynamicChild(DataModel dynamicDataModel, string key, string name = null, string description = null)
|
||||||
{
|
{
|
||||||
|
if (dynamicDataModel == null)
|
||||||
|
throw new ArgumentNullException(nameof(dynamicDataModel));
|
||||||
|
if (key == null)
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
|
||||||
if (_dynamicDataModels.ContainsKey(key))
|
if (_dynamicDataModels.ContainsKey(key))
|
||||||
{
|
{
|
||||||
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
|
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
|
||||||
@ -72,6 +78,12 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
$"because the dynamic data model is already added with key '{existingKey}.");
|
$"because the dynamic data model is already added with key '{existingKey}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamicDataModel.PluginInfo = PluginInfo;
|
||||||
|
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute()
|
||||||
|
{
|
||||||
|
Name = name ?? key.Humanize(),
|
||||||
|
Description = description
|
||||||
|
};
|
||||||
_dynamicDataModels.Add(key, dynamicDataModel);
|
_dynamicDataModels.Add(key, dynamicDataModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +112,8 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of data model you expect</typeparam>
|
/// <typeparam name="T">The type of data model you expect</typeparam>
|
||||||
/// <param name="key">The unique key of the dynamic data model</param>
|
/// <param name="key">The unique key of the dynamic data model</param>
|
||||||
/// <returns>If found, the dynamic data model</returns>
|
/// <returns>If found, the dynamic data model otherwise <c>null</c></returns>
|
||||||
public T GetDynamicChild<T>(string key) where T : DataModel
|
public T DynamicChild<T>(string key) where T : DataModel
|
||||||
{
|
{
|
||||||
_dynamicDataModels.TryGetValue(key, out var value);
|
_dynamicDataModels.TryGetValue(key, out var value);
|
||||||
return value as T;
|
return value as T;
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
using Artemis.Core.DataModelExpansions;
|
||||||
|
|
||||||
|
namespace Artemis.Plugins.DataModelExpansions.TestData.DataModels
|
||||||
|
{
|
||||||
|
public class DynamicDataModel : DataModel
|
||||||
|
{
|
||||||
|
public DynamicDataModel()
|
||||||
|
{
|
||||||
|
DynamicString = "Test 123";
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataModelProperty(Description = "Descriptionnnnnn")]
|
||||||
|
public string DynamicString { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using Artemis.Core;
|
||||||
using Artemis.Core.DataModelExpansions;
|
using Artemis.Core.DataModelExpansions;
|
||||||
using Artemis.Plugins.DataModelExpansions.TestData.DataModels;
|
using Artemis.Plugins.DataModelExpansions.TestData.DataModels;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@ -14,12 +14,13 @@ namespace Artemis.Plugins.DataModelExpansions.TestData
|
|||||||
{
|
{
|
||||||
_rand = new Random();
|
_rand = new Random();
|
||||||
AddTimedUpdate(TimeSpan.FromSeconds(1), TimedUpdate);
|
AddTimedUpdate(TimeSpan.FromSeconds(1), TimedUpdate);
|
||||||
}
|
|
||||||
|
|
||||||
private void TimedUpdate(double deltaTime)
|
DataModel.AddDynamicChild(new DynamicDataModel(), "Dynamic1", "Dynamic data model 1");
|
||||||
{
|
|
||||||
DataModel.TestColorA = SKColor.FromHsv(_rand.Next(0, 360), 100, 100);
|
// var testPath1 = new DataModelPath(DataModel, "TemplateDataModelString");
|
||||||
DataModel.TestColorB = SKColor.FromHsv(_rand.Next(0, 360), 100, 100);
|
var testPath2 = new DataModelPath(DataModel, "PluginSubDataModel.Number");
|
||||||
|
// var testPath3 = new DataModelPath(DataModel, "Dynamic1.DynamicString");
|
||||||
|
// var testPath4 = new DataModelPath(DataModel, "TemplateDataModelString");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DisablePlugin()
|
public override void DisablePlugin()
|
||||||
@ -30,6 +31,15 @@ namespace Artemis.Plugins.DataModelExpansions.TestData
|
|||||||
{
|
{
|
||||||
// You can access your data model here and update it however you like
|
// You can access your data model here and update it however you like
|
||||||
DataModel.TemplateDataModelString = $"The last delta time was {deltaTime} seconds";
|
DataModel.TemplateDataModelString = $"The last delta time was {deltaTime} seconds";
|
||||||
|
|
||||||
|
var dynamic = DataModel.DynamicChild<DynamicDataModel>("Dynamic1")?.DynamicString;
|
||||||
|
var dynamic2 = DataModel.DynamicChild<DynamicDataModel>("Dynamic2")?.DynamicString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimedUpdate(double deltaTime)
|
||||||
|
{
|
||||||
|
DataModel.TestColorA = SKColor.FromHsv(_rand.Next(0, 360), 100, 100);
|
||||||
|
DataModel.TestColorB = SKColor.FromHsv(_rand.Next(0, 360), 100, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user