diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs index ef1261628..55da596fa 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs @@ -13,6 +13,7 @@ namespace Artemis.Core public class DataModelConditionList : DataModelConditionPart { private bool _disposed; + private bool _reinitializing; /// /// Creates a new instance of the class @@ -83,6 +84,7 @@ namespace Artemis.Core ListPath?.Dispose(); ListPath = path != null ? new DataModelPath(path) : null; + SubscribeToListPath(); // Remove the old root group that was tied to the old data model while (Children.Any()) @@ -143,6 +145,10 @@ namespace Artemis.Core internal override void Save() { + // Don't save an invalid state + if (ListPath != null && !ListPath.IsValid) + return; + // Target list ListPath?.Save(); Entity.ListPath = ListPath?.Entity; @@ -164,6 +170,9 @@ namespace Artemis.Core internal void Initialize() { + while (Children.Any()) + RemoveChild(Children[0]); + if (Entity.ListPath == null) return; @@ -175,6 +184,7 @@ namespace Artemis.Core return; ListPath = listPath; + SubscribeToListPath(); if (ListPath.IsValid) { ListType = listType.GetGenericArguments()[0]; @@ -187,7 +197,7 @@ namespace Artemis.Core } // There should only be one child and it should be a group - if (Entity.Children.SingleOrDefault() is DataModelConditionGroupEntity rootGroup) + if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup) { AddChild(new DataModelConditionGroup(this, rootGroup)); } @@ -197,6 +207,39 @@ namespace Artemis.Core AddChild(new DataModelConditionGroup(this)); } } + + private void SubscribeToListPath() + { + if (ListPath == null) return; + ListPath.PathValidated += ListPathOnPathValidated; + ListPath.PathInvalidated += ListPathOnPathInvalidated; + } + + #region Event handlers + + private void ListPathOnPathValidated(object? sender, EventArgs e) + { + if (_reinitializing) + return; + + _reinitializing = true; + ListPath?.Dispose(); + Initialize(); + _reinitializing = false; + } + + private void ListPathOnPathInvalidated(object? sender, EventArgs e) + { + if (_reinitializing) + return; + + _reinitializing = true; + ListPath?.Dispose(); + Initialize(); + _reinitializing = false; + } + + #endregion } /// diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs index c37ebf1c1..d7b107c74 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs @@ -205,6 +205,10 @@ namespace Artemis.Core internal override void Save() { + // Don't save an invalid state + if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid) + return; + Entity.PredicateType = (int) PredicateType; LeftPath?.Save(); diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index ddb682a2a..c1e86d9cf 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -7,8 +6,6 @@ namespace Artemis.Core /// public class DataBinding : IDataBinding { - private readonly List> _modifiers = new List>(); - private TProperty _currentValue; private bool _disposed; private TimeSpan _easingProgress; @@ -63,51 +60,9 @@ namespace Artemis.Core /// Gets ors ets the easing function of the data binding /// public Easings.Functions EasingFunction { get; set; } - - + internal DataBindingEntity Entity { get; } - - /// - /// Updates the smoothing progress of the data binding - /// - /// The time in seconds that passed since the last update - public void Update(double deltaTime) - { - if (_disposed) - throw new ObjectDisposedException("DataBinding"); - - // Data bindings cannot go back in time like brushes - deltaTime = Math.Max(0, deltaTime); - - _easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime)); - if (_easingProgress > EasingTime) - _easingProgress = EasingTime; - } - - /// - public void Apply() - { - if (_disposed) - throw new ObjectDisposedException("DataBinding"); - - if (Converter == null) - return; - - TProperty converterValue = Converter.GetValue(); - TProperty value = GetValue(converterValue); - Converter.ApplyValue(value); - } - - /// - public void Dispose() - { - _disposed = true; - - Registration.DataBinding = null; - DataBindingMode?.Dispose(); - } - - + /// /// Gets the current value of the data binding /// @@ -179,6 +134,46 @@ namespace Artemis.Core return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction)); } + /// + /// Updates the smoothing progress of the data binding + /// + /// The time in seconds that passed since the last update + public void Update(double deltaTime) + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + // Data bindings cannot go back in time like brushes + deltaTime = Math.Max(0, deltaTime); + + _easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime)); + if (_easingProgress > EasingTime) + _easingProgress = EasingTime; + } + + /// + public void Apply() + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + if (Converter == null) + return; + + TProperty converterValue = Converter.GetValue(); + TProperty value = GetValue(converterValue); + Converter.ApplyValue(value); + } + + /// + public void Dispose() + { + _disposed = true; + + Registration.DataBinding = null; + DataBindingMode?.Dispose(); + } + #region Mode management /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index 4d89aa732..c8f2301ac 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -82,7 +82,7 @@ namespace Artemis.Core /// /// Converts the provided object to a type of /// - public virtual TProperty ConvertFromObject(object source) + public virtual TProperty ConvertFromObject(object? source) { return (TProperty) Convert.ChangeType(source, typeof(TProperty)); } diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DataBindingModifier.cs index 8b29c8ea6..d570463de 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DataBindingModifier.cs @@ -1,6 +1,4 @@ using System; -using System.Linq.Expressions; -using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Profile.DataBindings; using Newtonsoft.Json; @@ -50,14 +48,9 @@ namespace Artemis.Core public int Order { get; set; } /// - /// Gets the currently used instance of the parameter data model + /// Gets the path of the parameter property /// - public DataModel ParameterDataModel { get; private set; } - - /// - /// Gets the path of the parameter property in the - /// - public string ParameterPropertyPath { get; private set; } + public DataModelPath? ParameterPath { get; set; } /// /// Gets the parameter static value, only used it is @@ -65,13 +58,144 @@ namespace Artemis.Core /// public object ParameterStaticValue { get; private set; } - /// - /// A compiled expression tree that when given a matching data model returns the value of the modifiers parameter - /// - public Func CompiledParameterAccessor { get; set; } - internal DataBindingModifierEntity Entity { get; set; } + /// + /// Applies the modifier to the provided value + /// + /// The value to apply the modifier to, should be of the same type as the data binding target + /// The modified value + public object Apply(object? currentValue) + { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + + if (ModifierType == null) + return currentValue; + + if (!ModifierType.SupportsParameter) + return ModifierType.Apply(currentValue, null); + + if (ParameterType == ProfileRightSideType.Dynamic && ParameterPath != null && ParameterPath.IsValid) + { + object? value = ParameterPath.GetValue(); + return ModifierType.Apply(currentValue, value); + } + + if (ParameterType == ProfileRightSideType.Static) + return ModifierType.Apply(currentValue, ParameterStaticValue); + + return currentValue; + } + + /// + /// Updates the modifier type of the modifier and re-compiles the expression + /// + /// + public void UpdateModifierType(DataBindingModifierType modifierType) + { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + + // Calling CreateExpression will clear compiled expressions + if (modifierType == null) + { + ModifierType = null; + return; + } + + Type targetType = DirectDataBinding.DataBinding.GetTargetType(); + if (!modifierType.SupportsType(targetType)) + throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " + + $"it does not support this data binding's type {targetType.Name}"); + + ModifierType = modifierType; + } + + /// + /// Updates the parameter of the modifier and makes the modifier dynamic + /// + /// The path pointing to the parameter + public void UpdateParameterDynamic(DataModelPath? path) + { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + + if (path != null && !path.IsValid) + throw new ArtemisCoreException("Cannot update parameter of data binding modifier to an invalid path"); + + ParameterPath?.Dispose(); + ParameterPath = path != null ? new DataModelPath(path) : null; + + ParameterType = ProfileRightSideType.Dynamic; + } + + /// + /// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression + /// + /// The static value to use as a parameter + public void UpdateParameterStatic(object staticValue) + { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + + ParameterType = ProfileRightSideType.Static; + ParameterPath?.Dispose(); + ParameterPath = null; + + Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); + + // If not null ensure the types match and if not, convert it + if (staticValue != null && staticValue.GetType() == parameterType) + ParameterStaticValue = staticValue; + else if (staticValue != null) + ParameterStaticValue = Convert.ChangeType(staticValue, parameterType); + // If null create a default instance for value types or simply make it null for reference types + else if (parameterType.IsValueType) + ParameterStaticValue = Activator.CreateInstance(parameterType); + else + ParameterStaticValue = null; + } + + private void Initialize() + { + DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded; + DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved; + + // Modifier type + if (Entity.ModifierTypePluginGuid != null && ModifierType == null) + { + DataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType; + if (modifierType != null) + UpdateModifierType(modifierType); + } + + // Dynamic parameter + if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterPath != null) + { + ParameterPath = new DataModelPath(null, Entity.ParameterPath); + } + // Static parameter + else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null) + { + // Use the target type so JSON.NET has a better idea what to do + Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); + object staticValue; + + try + { + staticValue = JsonConvert.DeserializeObject(Entity.ParameterStaticValue, parameterType); + } + // If deserialization fails, use the type's default + catch (JsonSerializationException e) + { + DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e); + staticValue = Activator.CreateInstance(parameterType); + } + + UpdateParameterStatic(staticValue); + } + } /// public void Save() @@ -79,6 +203,10 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBindingModifier"); + // Don't save an invalid state + if (ParameterPath != null && !ParameterPath.IsValid) + return; + if (!DirectDataBinding.Entity.Modifiers.Contains(Entity)) DirectDataBinding.Entity.Modifiers.Add(Entity); @@ -94,11 +222,8 @@ namespace Artemis.Core Entity.ParameterType = (int) ParameterType; // Parameter - if (ParameterDataModel != null) - { - Entity.ParameterDataModelGuid = ParameterDataModel.PluginInfo.Guid; - Entity.ParameterPropertyPath = ParameterPropertyPath; - } + ParameterPath?.Save(); + Entity.ParameterPath = ParameterPath?.Entity; Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue); } @@ -125,186 +250,8 @@ namespace Artemis.Core DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded; DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved; - DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; - } - /// - /// Applies the modifier to the provided value - /// - /// The value to apply the modifier to, should be of the same type as the data binding target - /// The modified value - public object Apply(object currentValue) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - if (ModifierType == null) - return currentValue; - - if (!ModifierType.SupportsParameter) - return ModifierType.Apply(currentValue, null); - - if (ParameterType == ProfileRightSideType.Dynamic && CompiledParameterAccessor != null) - { - object value = CompiledParameterAccessor(ParameterDataModel); - return ModifierType.Apply(currentValue, value); - } - - if (ParameterType == ProfileRightSideType.Static) - return ModifierType.Apply(currentValue, ParameterStaticValue); - - return currentValue; - } - - /// - /// Updates the modifier type of the modifier and re-compiles the expression - /// - /// - public void UpdateModifierType(DataBindingModifierType modifierType) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - // Calling CreateExpression will clear compiled expressions - if (modifierType == null) - { - ModifierType = null; - CreateExpression(); - return; - } - - Type targetType = DirectDataBinding.DataBinding.GetTargetType(); - if (!modifierType.SupportsType(targetType)) - { - throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " + - $"it does not support this data binding's type {targetType.Name}"); - } - - ModifierType = modifierType; - CreateExpression(); - } - - /// - /// Updates the parameter of the modifier, makes the modifier dynamic and re-compiles the expression - /// - /// The data model of the parameter - /// The path pointing to the parameter inside the data model - public void UpdateParameter(DataModel? dataModel, string? path) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - if (dataModel != null && path == null) - throw new ArtemisCoreException("If a data model is provided, a path is also required"); - if (dataModel == null && path != null) - throw new ArtemisCoreException("If path is provided, a data model is also required"); - - if (dataModel != null) - { - if (!dataModel.ContainsPath(path)) - throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); - } - - ParameterType = ProfileRightSideType.Dynamic; - ParameterDataModel = dataModel; - ParameterPropertyPath = path; - - CreateExpression(); - } - - /// - /// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression - /// - /// The static value to use as a parameter - public void UpdateParameter(object staticValue) - { - if (_disposed) - throw new ObjectDisposedException("DataBindingModifier"); - - ParameterType = ProfileRightSideType.Static; - ParameterDataModel = null; - ParameterPropertyPath = null; - - Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); - - // If not null ensure the types match and if not, convert it - if (staticValue != null && staticValue.GetType() == parameterType) - ParameterStaticValue = staticValue; - else if (staticValue != null) - ParameterStaticValue = Convert.ChangeType(staticValue, parameterType); - // If null create a default instance for value types or simply make it null for reference types - else if (parameterType.IsValueType) - ParameterStaticValue = Activator.CreateInstance(parameterType); - else - ParameterStaticValue = null; - - CreateExpression(); - } - - private void Initialize() - { - DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded; - DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved; - DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; - - // Modifier type - if (Entity.ModifierTypePluginGuid != null && ModifierType == null) - { - DataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType; - if (modifierType != null) - UpdateModifierType(modifierType); - } - - // Dynamic parameter - if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterDataModelGuid != null && ParameterDataModel == null) - { - DataModel dataModel = DataModelStore.Get(Entity.ParameterDataModelGuid.Value)?.DataModel; - if (dataModel != null && dataModel.ContainsPath(Entity.ParameterPropertyPath)) - UpdateParameter(dataModel, Entity.ParameterPropertyPath); - } - // Static parameter - else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null) - { - // Use the target type so JSON.NET has a better idea what to do - Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType(); - object staticValue; - - try - { - staticValue = JsonConvert.DeserializeObject(Entity.ParameterStaticValue, parameterType); - } - // If deserialization fails, use the type's default - catch (JsonSerializationException e) - { - DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e); - staticValue = Activator.CreateInstance(parameterType); - } - - UpdateParameter(staticValue); - } - } - - private void CreateExpression() - { - CompiledParameterAccessor = null; - - if (ModifierType == null) - return; - - if (ParameterType == ProfileRightSideType.Dynamic && ModifierType.SupportsParameter) - { - if (ParameterDataModel == null) - return; - - // If the right side value is null, the constant type cannot be inferred and must be provided based on the data binding target - Expression parameterAccessor = ExpressionUtilities.CreateDataModelAccessor( - ParameterDataModel, ParameterPropertyPath, "parameter", out ParameterExpression rightSideParameter - ); - Expression> lambda = Expression.Lambda>(Expression.Convert(parameterAccessor, typeof(object)), rightSideParameter); - CompiledParameterAccessor = lambda.Compile(); - } + ParameterPath?.Dispose(); } #region Event handlers @@ -325,21 +272,6 @@ namespace Artemis.Core UpdateModifierType(null); } - private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) - { - DataModel dataModel = e.Registration.DataModel; - if (dataModel.PluginInfo.Guid == Entity.ParameterDataModelGuid && dataModel.ContainsPath(Entity.ParameterPropertyPath)) - UpdateParameter(dataModel, Entity.ParameterPropertyPath); - } - - private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) - { - if (e.Registration.DataModel != ParameterDataModel) - return; - ParameterDataModel = null; - CompiledParameterAccessor = null; - } - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs index b92144f42..f22ee61d5 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -20,31 +17,20 @@ namespace Artemis.Core DataBinding = dataBinding; Entity = entity; - Initialize(); Load(); } - internal DirectDataBindingEntity Entity { get; } - /// - /// Gets the currently used instance of the data model that contains the source of the data binding + /// Gets the path of the source property /// - public DataModel SourceDataModel { get; private set; } - - /// - /// Gets the path of the source property in the - /// - public string SourcePropertyPath { get; private set; } + public DataModelPath? SourcePath { get; private set; } /// /// Gets a list of modifiers applied to this data binding /// public IReadOnlyList> Modifiers => _modifiers.AsReadOnly(); - /// - /// Gets the compiled function that gets the current value of the data binding target - /// - public Func CompiledTargetAccessor { get; private set; } + internal DirectDataBindingEntity Entity { get; } /// public DataBinding DataBinding { get; } @@ -55,10 +41,10 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DirectDataBinding"); - if (CompiledTargetAccessor == null) + if (SourcePath == null || !SourcePath.IsValid) return baseValue; - object dataBindingValue = CompiledTargetAccessor(SourceDataModel); + object? dataBindingValue = SourcePath.GetValue(); foreach (DataBindingModifier dataBindingModifier in Modifiers) dataBindingValue = dataBindingModifier.Apply(dataBindingValue); @@ -72,11 +58,10 @@ namespace Artemis.Core { _disposed = true; - DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; - foreach (DataBindingModifier dataBindingModifier in Modifiers) dataBindingModifier.Dispose(); + + SourcePath?.Dispose(); } #endregion @@ -86,7 +71,9 @@ namespace Artemis.Core /// public void Load() { - // Data model is done during Initialize + // Source + if (Entity.SourcePath != null) + SourcePath = new DataModelPath(null, Entity.SourcePath); // Modifiers foreach (DataBindingModifierEntity dataBindingModifierEntity in Entity.Modifiers) @@ -98,12 +85,12 @@ namespace Artemis.Core /// public void Save() { - // Data model - if (SourceDataModel != null) - { - Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid; - Entity.SourcePropertyPath = SourcePropertyPath; - } + // Don't save an invalid state + if (SourcePath != null && !SourcePath.IsValid) + return; + + SourcePath?.Save(); + Entity.SourcePath = SourcePath?.Entity; // Modifiers Entity.Modifiers.Clear(); @@ -113,76 +100,30 @@ namespace Artemis.Core #endregion - #region Initialization - - private void Initialize() - { - DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; - - // Source - if (Entity.SourceDataModelGuid != null && SourceDataModel == null) - { - DataModel dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel; - if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath)) - UpdateSource(dataModel, Entity.SourcePropertyPath); - } - } - - private void CreateExpression() - { - Type listType = SourceDataModel.GetListTypeInPath(SourcePropertyPath); - if (listType != null) - throw new ArtemisCoreException($"Cannot create a regular accessor at path {SourcePropertyPath} because the path contains a list"); - - ParameterExpression parameter = Expression.Parameter(typeof(DataModel), "targetDataModel"); - Expression accessor = SourcePropertyPath.Split('.').Aggregate( - Expression.Convert(parameter, SourceDataModel.GetType()), // Cast to the appropriate type - Expression.Property - ); - - UnaryExpression returnValue = Expression.Convert(accessor, typeof(object)); - - Expression> lambda = Expression.Lambda>(returnValue, parameter); - CompiledTargetAccessor = lambda.Compile(); - } - - #endregion - #region Source /// /// Returns the type of the source property of this data binding /// - public Type GetSourceType() + public Type? GetSourceType() { - return SourceDataModel?.GetTypeAtPath(SourcePropertyPath); + return SourcePath?.GetPropertyType(); } /// - /// Updates the source of the data binding and re-compiles the expression + /// Updates the source of the data binding /// - /// The data model of the source - /// The path pointing to the source inside the data model - public void UpdateSource(DataModel dataModel, string path) + /// The path pointing to the source + public void UpdateSource(DataModelPath? path) { if (_disposed) throw new ObjectDisposedException("DirectDataBinding"); - if (dataModel != null && path == null) - throw new ArtemisCoreException("If a data model is provided, a path is also required"); - if (dataModel == null && path != null) - throw new ArtemisCoreException("If path is provided, a data model is also required"); + if (path != null && !path.IsValid) + throw new ArtemisCoreException("Cannot update source of data binding to an invalid path"); - if (dataModel != null) - { - if (!dataModel.ContainsPath(path)) - throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); - } - - SourceDataModel = dataModel; - SourcePropertyPath = path; - CreateExpression(); + SourcePath?.Dispose(); + SourcePath = path != null ? new DataModelPath(path) : null; } #endregion @@ -239,31 +180,12 @@ namespace Artemis.Core #endregion - #region Event handlers - - private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) - { - DataModel dataModel = e.Registration.DataModel; - if (dataModel.PluginInfo.Guid == Entity.SourceDataModelGuid && dataModel.ContainsPath(Entity.SourcePropertyPath)) - UpdateSource(dataModel, Entity.SourcePropertyPath); - } - - private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) - { - if (SourceDataModel != e.Registration.DataModel) - return; - SourceDataModel = null; - CompiledTargetAccessor = null; - } - - #endregion - #region Events /// /// Occurs when a modifier is added or removed /// - public event EventHandler ModifiersUpdated; + public event EventHandler? ModifiersUpdated; protected virtual void OnModifiersUpdated() { diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 78cc14677..1126b8efd 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -14,9 +14,9 @@ namespace Artemis.Core /// public class DataModelPath : IStorageModel, IDisposable { - private bool _disposed; private readonly LinkedList _segments; private Expression>? _accessorLambda; + private bool _disposed; /// /// Creates a new instance of the class pointing directly to the target @@ -59,7 +59,7 @@ namespace Artemis.Core /// The path to base the new instance on public DataModelPath(DataModelPath dataModelPath) { - if (dataModelPath == null) + if (dataModelPath == null) throw new ArgumentNullException(nameof(dataModelPath)); Target = dataModelPath.Target; @@ -188,6 +188,8 @@ namespace Artemis.Core _accessorLambda = null; Accessor = null; + + OnPathInvalidated(); } internal void Initialize() @@ -239,6 +241,9 @@ namespace Artemis.Core ), parameter ); + + if (IsValid) + OnPathValidated(); } private void SubscribeToDataModelStore() @@ -307,5 +312,29 @@ namespace Artemis.Core } #endregion + + #region Events + + /// + /// Occurs whenever the path becomes invalid + /// + public event EventHandler PathInvalidated; + + /// + /// Occurs whenever the path becomes valid + /// + public event EventHandler PathValidated; + + protected virtual void OnPathValidated() + { + PathValidated?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnPathInvalidated() + { + PathInvalidated?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 8e5e54dab..824649181 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -166,8 +166,8 @@ namespace Artemis.Core.Services _frameStopWatch.Restart(); lock (_dataModelExpansions) { - // Update all active modules - foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions) + // Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated + foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.Enabled)) dataModelExpansion.Update(args.DeltaTime); } diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingModifierEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingModifierEntity.cs index cef799f14..c4139737c 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingModifierEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingModifierEntity.cs @@ -10,8 +10,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings public int Order { get; set; } public int ParameterType { get; set; } - public Guid? ParameterDataModelGuid { get; set; } - public string ParameterPropertyPath { get; set; } + public DataModelPathEntity ParameterPath { get; set; } // Stored as a string to be able to control serialization and deserialization ourselves public string ParameterStaticValue { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs index ce1fc189e..e8a14cb9f 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Artemis.Storage.Entities.Profile.DataBindings { @@ -10,9 +9,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings Modifiers = new List(); } - public Guid? SourceDataModelGuid { get; set; } - public string SourcePropertyPath { get; set; } - + public DataModelPathEntity SourcePath { get; set; } public List Modifiers { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml index 5a0512ba4..56350154b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml @@ -146,7 +146,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 0de5a854f..153e9ca40 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -20,10 +20,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings private bool _isEasingTimeEnabled; private DataBindingModeType _selectedDataBindingMode; private TimelineEasingViewModel _selectedEasingViewModel; - - private bool _updating; private bool _applyTestResultToLayer; + private bool _updating; + private bool _updatingTestResult; public DataBindingViewModel(DataBindingRegistration registration, IProfileEditorService profileEditorService, @@ -74,7 +74,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings public bool ApplyTestResultToLayer { get => _applyTestResultToLayer; - set => SetAndNotify(ref _applyTestResultToLayer, value); + set + { + if (!SetAndNotify(ref _applyTestResultToLayer, value)) return; + _profileEditorService.UpdateProfilePreview(); + } } public TimelineEasingViewModel SelectedEasingViewModel @@ -111,6 +115,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { _updateTimer.Dispose(); _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + + Registration.LayerProperty.Updated -= LayerPropertyOnUpdated; } private void Initialize() @@ -119,6 +125,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _updateTimer.Start(); _updateTimer.Elapsed += OnUpdateTimerOnElapsed; + Registration.LayerProperty.Updated += LayerPropertyOnUpdated; CreateDataBindingModeModeViewModel(); Update(); @@ -216,14 +223,22 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings private void UpdateTestResult() { + if (_updating || _updatingTestResult) + return; + + _updatingTestResult = true; + if (Registration.DataBinding == null || ActiveItem == null) { TestInputValue.UpdateValue(default); TestResultValue.UpdateValue(default); + _updatingTestResult = false; return; } + // While playing in preview data bindings aren't updated Registration.DataBinding.Update(0.04); + if (ActiveItem.SupportsTestValue) { TProperty currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty)); @@ -235,11 +250,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings if (ApplyTestResultToLayer) { - Registration.DataBinding.Apply(); - _profileEditorService.UpdateProfilePreview(); + // TODO: A bit crappy, the ProfileEditorService should really be doing this + bool playing = ((Parent as Screen)?.Parent as LayerPropertiesViewModel)?.Playing ?? true; + if (!playing) + _profileEditorService.UpdateProfilePreview(); } + + _updatingTestResult = false; } - + private void EnableDataBinding() { if (Registration.DataBinding != null) @@ -264,6 +283,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { UpdateTestResult(); } + + private void LayerPropertyOnUpdated(object? sender, LayerPropertyEventArgs e) + { + if (ApplyTestResultToLayer) + Registration.DataBinding?.Apply(); + } } public interface IDataBindingViewModel : IScreen, IDisposable diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs index de58da9d9..e4d1b57e9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs @@ -94,10 +94,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa if (Modifier.ParameterType == ProfileRightSideType.Dynamic) { Type sourceType = Modifier.DirectDataBinding.GetSourceType(); - Modifier.UpdateParameter((Modifier.ModifierType.ParameterType ?? sourceType).GetDefault()); + Modifier.UpdateParameterStatic((Modifier.ModifierType.ParameterType ?? sourceType).GetDefault()); } else - Modifier.UpdateParameter(null, null); + Modifier.UpdateParameterDynamic(null); Update(); _profileEditorService.UpdateSelectedProfileElement(); @@ -105,13 +105,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) { - Modifier.UpdateParameter(e.DataModelPath.Target, e.DataModelPath.Path); + Modifier.UpdateParameterDynamic(e.DataModelPath); _profileEditorService.UpdateSelectedProfileElement(); } private void StaticInputViewModelOnValueUpdated(object sender, DataModelInputStaticEventArgs e) { - Modifier.UpdateParameter(e.Value); + Modifier.UpdateParameterStatic(e.Value); _profileEditorService.UpdateSelectedProfileElement(); } @@ -166,11 +166,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa SelectedModifierType = Modifier.ModifierType; // Parameter - throw new NotImplementedException(); - // if (DynamicSelectionViewModel != null) - // DynamicSelectionViewModel.PopulateSelectedPropertyViewModel(Modifier.ParameterDataModel, Modifier.ParameterPropertyPath); - // else if (StaticInputViewModel != null) - // StaticInputViewModel.Value = Modifier.ParameterStaticValue; + if (DynamicSelectionViewModel != null) + DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath); + else if (StaticInputViewModel != null) + StaticInputViewModel.Value = Modifier.ParameterStaticValue; } private void ExecuteSelectModifierTypeCommand(object context) diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs index a74d645fd..cfaa109b3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs @@ -56,18 +56,16 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa public void Update() { - throw new NotImplementedException(); - // TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DirectDataBinding.SourceDataModel, DirectDataBinding.SourcePropertyPath); + TargetSelectionViewModel.ChangeDataModelPath(DirectDataBinding.SourcePath); TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()}; - CanAddModifier = DirectDataBinding.SourceDataModel != null; + CanAddModifier = DirectDataBinding.SourcePath != null && DirectDataBinding.SourcePath.IsValid; UpdateModifierViewModels(); } public object GetTestValue() { - throw new NotImplementedException(); - // return TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue(); + return DirectDataBinding.SourcePath?.GetValue(); } #region IDisposable @@ -114,8 +112,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) { - throw new NotImplementedException(); - // DirectDataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.Path); + DirectDataBinding.UpdateSource(e.DataModelPath); Update(); _profileEditorService.UpdateSelectedProfileElement(); @@ -143,6 +140,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa modifierViewModel.Dispose(); } + // Don't create children without a source or with an invalid source + if (DirectDataBinding.SourcePath == null || !DirectDataBinding.SourcePath.IsValid) + return; + // Add missing VMs foreach (DataBindingModifier modifier in DirectDataBinding.Modifiers) {