diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index b572a03ac..c95a2be2e 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Reflection; using Artemis.Core.Plugins.Models; namespace Artemis.Core diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/LayerConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/LayerConditionPart.cs new file mode 100644 index 000000000..7293b8c5f --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/LayerConditionPart.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Artemis.Core.Models.Profile.Conditions.Abstract +{ + public abstract class LayerConditionPart + { + protected LayerConditionPart() + { + Children = new List(); + } + + public List Children { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs index fb91ef3b6..08236fd68 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs @@ -7,5 +7,6 @@ namespace Artemis.Core.Models.Profile.Conditions public class LayerCondition { public Expression> ExpressionTree { get; set; } + public LayerConditionGroup RootGroup { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/LayerConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionGroup.cs new file mode 100644 index 000000000..dbcc19ad4 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionGroup.cs @@ -0,0 +1,17 @@ +using Artemis.Core.Models.Profile.Conditions.Abstract; + +namespace Artemis.Core.Models.Profile.Conditions +{ + public class LayerConditionGroup : LayerConditionPart + { + public BooleanOperator BooleanOperator { get; set; } + } + + public enum BooleanOperator + { + And, + Or, + AndNot, + OrNot + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/LayerConditionListStatement.cs b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionListStatement.cs new file mode 100644 index 000000000..c50eeb815 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionListStatement.cs @@ -0,0 +1,18 @@ +using Artemis.Core.Models.Profile.Conditions.Abstract; + +namespace Artemis.Core.Models.Profile.Conditions +{ + public class LayerConditionListStatement : LayerConditionPart + { + public ListOperator ListOperator { get; set; } + + } + + public enum ListOperator + { + Any, + All, + None, + Count + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/LayerConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionOperator.cs new file mode 100644 index 000000000..78f7630b1 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionOperator.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Artemis.Core.Plugins.Models; +using Artemis.Core.Services.Interfaces; + +namespace Artemis.Core.Models.Profile.Conditions +{ + public abstract class LayerConditionOperator + { + private IDataModelService _dataModelService; + private bool _registered; + + /// + /// Gets the plugin info this condition operator belongs to + /// Note: Not set until after registering + /// + public PluginInfo PluginInfo { get; internal set; } + + /// + /// Gets the types this operator supports + /// + public abstract IReadOnlyCollection CompatibleTypes { get; } + + /// + /// Gets or sets the description of this logical operator + /// + public string Description { get; set; } + + /// + /// Gets or sets the icon of this logical operator + /// + public string Icon { get; set; } + + /// + /// Creates a binary expression comparing two types + /// + /// The type of parameter passed to the left side of the expression + /// The type of parameter passed to the right side of the expression + /// + public abstract BinaryExpression CreateExpression(Type leftSideType, Type rightSideType); + + internal void Register(PluginInfo pluginInfo, IDataModelService dataModelService) + { + if (_registered) + return; + + PluginInfo = pluginInfo; + _dataModelService = dataModelService; + + if (PluginInfo != Constants.CorePluginInfo) + PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; + + _registered = true; + } + + internal void Unsubscribe() + { + if (!_registered) + return; + + if (PluginInfo != Constants.CorePluginInfo) + PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; + _registered = false; + } + + private void InstanceOnPluginDisabled(object sender, EventArgs e) + { + // Profile editor service will call Unsubscribe + _dataModelService.RemoveConditionOperator(this); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/LayerConditionStatement.cs b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionStatement.cs new file mode 100644 index 000000000..522dce6a5 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/LayerConditionStatement.cs @@ -0,0 +1,14 @@ +using System; +using Artemis.Core.Models.Profile.Conditions.Abstract; + +namespace Artemis.Core.Models.Profile.Conditions +{ + public class LayerConditionStatement : LayerConditionPart + { + public Guid DataModelGuid { get; set; } + public string PropertyPath { get; set; } + + public LayerConditionOperator Operator { get; set; } + public object Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs new file mode 100644 index 000000000..d98b8d19c --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Artemis.Core.Models.Profile.Conditions.Operators +{ + public class GreaterThanConditionOperator : LayerConditionOperator + { + public override IReadOnlyCollection CompatibleTypes => new List + { + typeof(sbyte), + typeof(byte), + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(float), + typeof(double), + typeof(decimal) + }; + + public override BinaryExpression CreateExpression(Type leftSideType, Type rightSideType) + { + var leftSideParameter = Expression.Parameter(leftSideType, "a"); + var rightSideParameter = Expression.Parameter(rightSideType, "b"); + return Expression.GreaterThan(leftSideParameter, rightSideParameter); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/DataModelService.cs b/src/Artemis.Core/Services/DataModelService.cs index fb56e547b..1754d42c9 100644 --- a/src/Artemis.Core/Services/DataModelService.cs +++ b/src/Artemis.Core/Services/DataModelService.cs @@ -1,11 +1,15 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Events; using Artemis.Core.Exceptions; +using Artemis.Core.Models.Profile.Conditions; +using Artemis.Core.Models.Profile.Conditions.Operators; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Exceptions; +using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; namespace Artemis.Core.Services @@ -17,15 +21,19 @@ namespace Artemis.Core.Services { private readonly List _dataModelExpansions; private readonly IPluginService _pluginService; + private readonly List _registeredConditionOperators; internal DataModelService(IPluginService pluginService) { _pluginService = pluginService; _dataModelExpansions = new List(); + _registeredConditionOperators = new List(); _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; + RegisterBuiltInConditionOperators(); + foreach (var module in _pluginService.GetPluginsOfType().Where(m => m.InternalExpandsMainDataModel)) AddModuleDataModel(module); foreach (var dataModelExpansion in _pluginService.GetPluginsOfType()) @@ -73,6 +81,52 @@ namespace Artemis.Core.Services return null; } + + public void RegisterConditionOperator(PluginInfo pluginInfo, LayerConditionOperator layerConditionOperator) + { + if (pluginInfo == null) + throw new ArgumentNullException(nameof(pluginInfo)); + if (layerConditionOperator == null) + throw new ArgumentNullException(nameof(layerConditionOperator)); + + lock (_registeredConditionOperators) + { + if (_registeredConditionOperators.Contains(layerConditionOperator)) + return; + + layerConditionOperator.Register(pluginInfo, this); + _registeredConditionOperators.Add(layerConditionOperator); + } + } + + public void RemoveConditionOperator(LayerConditionOperator layerConditionOperator) + { + if (layerConditionOperator == null) + throw new ArgumentNullException(nameof(layerConditionOperator)); + + lock (_registeredConditionOperators) + { + if (!_registeredConditionOperators.Contains(layerConditionOperator)) + return; + + layerConditionOperator.Unsubscribe(); + _registeredConditionOperators.Remove(layerConditionOperator); + } + } + + public List GetCompatibleConditionOperators(Type type) + { + lock (_registeredConditionOperators) + { + return _registeredConditionOperators.Where(c => c.CompatibleTypes.Contains(type)).ToList(); + } + } + + private void RegisterBuiltInConditionOperators() + { + RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator()); + } + private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) { if (e.PluginInfo.Instance is Module module && module.InternalExpandsMainDataModel) @@ -85,7 +139,7 @@ namespace Artemis.Core.Services { if (dataModelExpansion.InternalDataModel.DataModelDescription == null) throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null"); - + AddExpansion(dataModelExpansion.InternalDataModel); } @@ -93,7 +147,7 @@ namespace Artemis.Core.Services { if (module.InternalDataModel.DataModelDescription == null) throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null"); - + AddExpansion(module.InternalDataModel); } diff --git a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs index 60efedd3f..6f2fe6ba0 100644 --- a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs +++ b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; -using Artemis.Core.Models; +using Artemis.Core.Annotations; +using Artemis.Core.Models.Profile.Conditions; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.DataModels; +using Artemis.Core.Plugins.Models; namespace Artemis.Core.Services.Interfaces { @@ -22,9 +24,22 @@ namespace Artemis.Core.Services.Interfaces void RemoveExpansion(DataModel baseDataModelExpansion); /// - /// If found, returns the data model of the provided plugin + /// If found, returns the data model of the provided plugin /// /// Should be a module with a data model or a data model expansion DataModel GetPluginDataModel(Plugin plugin); + + /// + /// Registers a new condition operator for use in layer conditions + /// + /// The PluginInfo of the plugin this condition operator belongs to + /// The condition operator to register + void RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] LayerConditionOperator layerConditionOperator); + + /// + /// Removes a condition operator so it is no longer available for use in layer conditions + /// + /// The layer condition operator to remove + void RemoveConditionOperator([NotNull] LayerConditionOperator layerConditionOperator); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs index 4810900e5..9907b02e6 100644 --- a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs @@ -57,7 +57,8 @@ namespace Artemis.UI.Shared.Services if (existing != null) { if (existing.PluginInfo != pluginInfo) - throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {pluginInfo.Name}"); + throw new ArtemisPluginException($"Cannot register data model input for type {supportedType.Name} " + + $"because an editor was already registered by {pluginInfo.Name}"); return existing; } @@ -78,7 +79,8 @@ namespace Artemis.UI.Shared.Services if (existing != null) { if (existing.PluginInfo != pluginInfo) - throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {pluginInfo.Name}"); + throw new ArtemisPluginException($"Cannot register data model display for type {supportedType.Name} " + + $"because an editor was already registered by {pluginInfo.Name}"); return existing; } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index c3ccbfe55..dfd8b8571 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -102,8 +102,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins if (PluginInfo.Icon != null) { var parsedIcon = Enum.TryParse(PluginInfo.Icon, true, out var iconEnum); - if (parsedIcon == false) - return PackIconKind.QuestionMarkCircle; + if (parsedIcon) + return iconEnum; } switch (Plugin)