From 04f2162bcd70d06524fc8d20ae66358e34386471 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 18 Aug 2020 19:06:19 +0200 Subject: [PATCH] Core - Implemented module priority Settings UI - Module priority modification UI WIP --- src/Artemis.Core/Plugins/Abstract/Module.cs | 56 ++++++-- src/Artemis.Core/Services/CoreService.cs | 37 +---- src/Artemis.Core/Services/ModuleService.cs | 131 ++++++++++++++++++ .../Entities/Module/ModuleSettingsEntity.cs | 11 ++ .../Interfaces/IModuleRepository.cs | 15 ++ .../Repositories/ModuleRepository.cs | 44 ++++++ .../Ninject/Factories/IVMFactory.cs | 10 +- .../ProfileTree/TreeItem/LayerView.xaml | 10 +- .../Screens/Settings/SettingsView.xaml | 9 ++ .../Screens/Settings/SettingsViewModel.cs | 22 +-- .../Tabs/Modules/ModuleOrderView.xaml | 123 ++++++++++++++++ .../Tabs/Modules/ModuleOrderViewModel.cs | 11 ++ 12 files changed, 416 insertions(+), 63 deletions(-) create mode 100644 src/Artemis.Core/Services/ModuleService.cs create mode 100644 src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs create mode 100644 src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs create mode 100644 src/Artemis.Storage/Repositories/ModuleRepository.cs create mode 100644 src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderViewModel.cs diff --git a/src/Artemis.Core/Plugins/Abstract/Module.cs b/src/Artemis.Core/Plugins/Abstract/Module.cs index c0a26a264..f008f4b7c 100644 --- a/src/Artemis.Core/Plugins/Abstract/Module.cs +++ b/src/Artemis.Core/Plugins/Abstract/Module.cs @@ -6,6 +6,7 @@ using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Artemis.Core.Plugins.Abstract.ViewModels; using Artemis.Core.Plugins.ModuleActivationRequirements; +using Artemis.Storage.Entities.Module; using SkiaSharp; namespace Artemis.Core.Plugins.Abstract @@ -68,12 +69,6 @@ namespace Artemis.Core.Plugins.Abstract /// public abstract class Module : Plugin { - protected Module() - { - ActivationRequirements = new List(); - ActivationRequirementMode = ActivationRequirementType.Any; - } - /// /// The modules display name that's shown in the menu /// @@ -95,16 +90,33 @@ namespace Artemis.Core.Plugins.Abstract /// A list of activation requirements /// Note: if empty the module is always activated /// - public List ActivationRequirements { get; } + public List ActivationRequirements { get; } = new List(); /// /// Gets or sets the activation requirement mode, defaults to /// - public ActivationRequirementType ActivationRequirementMode { get; set; } + public ActivationRequirementType ActivationRequirementMode { get; set; } = ActivationRequirementType.Any; + + /// + /// Gets or sets the default priority category for this module, defaults to + /// + /// + public ModulePriorityCategory DefaultPriorityCategory { get; set; } = ModulePriorityCategory.Normal; + + /// + /// Gets or sets the current priority category of this module + /// + public ModulePriorityCategory PriorityCategory { get; set; } + + /// + /// Gets or sets the current priority of this module within its priority category + /// + public int Priority { get; set; } internal DataModel InternalDataModel { get; set; } internal bool InternalExpandsMainDataModel { get; set; } + internal ModuleSettingsEntity Entity { get; set; } /// /// Called each frame when the module must update @@ -170,6 +182,16 @@ namespace Artemis.Core.Plugins.Abstract IsActivated = false; ModuleDeactivated(); } + + internal void ApplyToEntity() + { + if (Entity == null) + Entity = new ModuleSettingsEntity(); + + Entity.PluginGuid = PluginInfo.Guid; + Entity.PriorityCategory = (int) PriorityCategory; + Entity.Priority = Priority; + } } public enum ActivationRequirementType @@ -184,4 +206,22 @@ namespace Artemis.Core.Plugins.Abstract /// All } + + public enum ModulePriorityCategory + { + /// + /// Indicates a normal render priority + /// + Normal, + + /// + /// Indicates that the module renders for a specific application/game, rendering on top of normal modules + /// + Application, + + /// + /// Indicates that the module renders an overlay, always rendering on top + /// + Overlay + } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 5193b45d1..8b3677838 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -39,11 +39,10 @@ namespace Artemis.Core.Services private List _dataModelExpansions; private List _modules; private IntroAnimation _introAnimation; - private DateTime _lastModuleActivationUpdate; // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else internal CoreService(ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, - IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService) + IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService) { _logger = logger; _pluginService = pluginService; @@ -169,11 +168,6 @@ namespace Artemis.Core.Services try { _frameStopWatch.Restart(); - - // Only run the module activation update every 2 seconds - if (DateTime.Now - _lastModuleActivationUpdate > TimeSpan.FromSeconds(2)) - ModuleActivationUpdate(); - lock (_dataModelExpansions) { // Update all active modules @@ -184,7 +178,7 @@ namespace Artemis.Core.Services List modules; lock (_modules) { - modules = _modules.Where(m => m.IsActivated).ToList(); + modules = _modules.Where(m => m.IsActivated).OrderByDescending(m => m.PriorityCategory).ThenByDescending(m => m.Priority).ToList(); } // Update all active modules @@ -223,33 +217,6 @@ namespace Artemis.Core.Services } } - private void ModuleActivationUpdate() - { - _lastModuleActivationUpdate = DateTime.Now; - var stopwatch = new Stopwatch(); - stopwatch.Start(); - lock (_modules) - { - foreach (var module in _modules) - { - var shouldBeActivated = module.EvaluateActivationRequirements(); - if (shouldBeActivated && !module.IsActivated) - { - module.Activate(); - // If this is a profile module, activate the last active profile after module activation - if (module is ProfileModule profileModule) - _profileService.ActivateLastProfile(profileModule); - } - else if (!shouldBeActivated && module.IsActivated) - module.Deactivate(); - } - } - - stopwatch.Stop(); - if (stopwatch.ElapsedMilliseconds > 100) - _logger.Warning("Activation requirements evaluation took too long: {moduleCount} module(s) in {elapsed}", _modules.Count, stopwatch.Elapsed); - } - private void SurfaceOnUpdated(UpdatedEventArgs args) { if (_rgbService.IsRenderPaused) diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs new file mode 100644 index 000000000..76a9a39df --- /dev/null +++ b/src/Artemis.Core/Services/ModuleService.cs @@ -0,0 +1,131 @@ +using System.Diagnostics; +using System.Linq; +using System.Timers; +using Artemis.Core.Events; +using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Services.Interfaces; +using Artemis.Core.Services.Storage.Interfaces; +using Artemis.Storage.Repositories.Interfaces; +using Serilog; + +namespace Artemis.Core.Services +{ + public class ModuleService : IModuleService + { + private readonly ILogger _logger; + private readonly IModuleRepository _moduleRepository; + private readonly IPluginService _pluginService; + private readonly IProfileService _profileService; + + public ModuleService(ILogger logger, IModuleRepository moduleRepository, IPluginService pluginService, IProfileService profileService) + { + _logger = logger; + _moduleRepository = moduleRepository; + _pluginService = pluginService; + _profileService = profileService; + _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; + + var activationUpdateTimer = new Timer(2000); + activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed; + activationUpdateTimer.Start(); + PopulatePriorities(); + } + + private void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) + { + UpdateModuleActivation(); + } + + public void UpdateModuleActivation() + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var modules = _pluginService.GetPluginsOfType().ToList(); + foreach (var module in modules) + { + var shouldBeActivated = module.EvaluateActivationRequirements(); + if (shouldBeActivated && !module.IsActivated) + { + module.Activate(); + // If this is a profile module, activate the last active profile after module activation + if (module is ProfileModule profileModule) + _profileService.ActivateLastProfile(profileModule); + } + else if (!shouldBeActivated && module.IsActivated) + module.Deactivate(); + } + + stopwatch.Stop(); + if (stopwatch.ElapsedMilliseconds > 100) + _logger.Warning("Activation requirements evaluation took too long: {moduleCount} module(s) in {elapsed}", modules.Count, stopwatch.Elapsed); + } + + public void PopulatePriorities() + { + var modules = _pluginService.GetPluginsOfType().ToList(); + var moduleEntities = _moduleRepository.GetAll(); + + foreach (var module in modules) + { + var entity = moduleEntities.FirstOrDefault(e => e.PluginGuid == module.PluginInfo.Guid); + if (entity != null) + { + module.Entity = entity; + module.PriorityCategory = (ModulePriorityCategory) entity.PriorityCategory; + module.Priority = entity.Priority; + } + } + } + + public void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority) + { + var modules = _pluginService.GetPluginsOfType().Where(m => m.PriorityCategory == category).OrderBy(m => m.Priority).ToList(); + + if (modules.Contains(module)) + modules.Remove(module); + + if (modules.Count == 0) + priority = 1; + else if (priority < 1) + priority = 1; + else if (priority > modules.Count) + priority = modules.Count; + + module.PriorityCategory = category; + modules.Insert(priority - 1, module); + + for (var index = 0; index < modules.Count; index++) + { + var categoryModule = modules[index]; + categoryModule.Priority = index + 1; + categoryModule.ApplyToEntity(); + + _moduleRepository.Save(categoryModule.Entity); + } + } + + private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) + { + if (e.PluginInfo.Instance is Module module) + InitialiseOrApplyPriority(module); + } + + private void InitialiseOrApplyPriority(Module module) + { + var entity = _moduleRepository.GetByPluginGuid(module.PluginInfo.Guid); + if (entity != null) + { + module.Entity = entity; + module.PriorityCategory = (ModulePriorityCategory) entity.PriorityCategory; + module.Priority = entity.Priority; + } + else + UpdateModulePriority(module, module.DefaultPriorityCategory, 1); + } + } + + public interface IModuleService : IArtemisService + { + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs b/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs new file mode 100644 index 000000000..0560635a0 --- /dev/null +++ b/src/Artemis.Storage/Entities/Module/ModuleSettingsEntity.cs @@ -0,0 +1,11 @@ +using System; + +namespace Artemis.Storage.Entities.Module +{ + public class ModuleSettingsEntity + { + public Guid PluginGuid { get; set; } + public int PriorityCategory { get; set; } + public int Priority { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs new file mode 100644 index 000000000..5780e7bc0 --- /dev/null +++ b/src/Artemis.Storage/Repositories/Interfaces/IModuleRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Artemis.Storage.Entities.Module; + +namespace Artemis.Storage.Repositories.Interfaces +{ + public interface IModuleRepository : IRepository + { + void Add(ModuleSettingsEntity moduleSettingsEntity); + ModuleSettingsEntity GetByPluginGuid(Guid guid); + List GetAll(); + List GetByCategory(int category); + void Save(ModuleSettingsEntity moduleSettingsEntity); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ModuleRepository.cs b/src/Artemis.Storage/Repositories/ModuleRepository.cs new file mode 100644 index 000000000..9c0c3d51a --- /dev/null +++ b/src/Artemis.Storage/Repositories/ModuleRepository.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Artemis.Storage.Entities.Module; +using Artemis.Storage.Repositories.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Repositories +{ + public class ModuleRepository : IModuleRepository + { + private readonly LiteRepository _repository; + + internal ModuleRepository(LiteRepository repository) + { + _repository = repository; + _repository.Database.GetCollection().EnsureIndex(s => s.PluginGuid); + } + + public void Add(ModuleSettingsEntity moduleSettingsEntity) + { + _repository.Insert(moduleSettingsEntity); + } + + public ModuleSettingsEntity GetByPluginGuid(Guid guid) + { + return _repository.FirstOrDefault(s => s.PluginGuid == guid); + } + + public List GetAll() + { + return _repository.Query().ToList(); + } + + public List GetByCategory(int category) + { + return _repository.Query().Where(s => s.PriorityCategory == category).ToList(); + } + + public void Save(ModuleSettingsEntity moduleSettingsEntity) + { + _repository.Upsert(moduleSettingsEntity); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index e4f0c7b4b..6875eb055 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -32,14 +32,10 @@ namespace Artemis.UI.Ninject.Factories ModuleRootViewModel Create(Module module); } - public interface IPluginSettingsVmFactory : IVmFactory + public interface ISettingsVmFactory : IVmFactory { - PluginSettingsViewModel Create(Plugin plugin); - } - - public interface IDeviceSettingsVmFactory : IVmFactory - { - DeviceSettingsViewModel Create(ArtemisDevice device); + PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin); + DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device); } public interface IDeviceDebugVmFactory : IVmFactory diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml index 339ab0a7c..397dff827 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml @@ -28,12 +28,12 @@ - - - - + + + + - + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs index c520cb01d..a65f7e11e 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs @@ -11,6 +11,7 @@ using Artemis.Core.Services.Storage.Interfaces; using Artemis.Core.Utilities; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Settings.Tabs.Devices; +using Artemis.UI.Screens.Settings.Tabs.Modules; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces; @@ -22,12 +23,12 @@ namespace Artemis.UI.Screens.Settings { public class SettingsViewModel : MainScreenViewModel { + public ModuleOrderViewModel ModuleOrderViewModel { get; } private readonly IDebugService _debugService; - private readonly IDeviceSettingsVmFactory _deviceSettingsVmFactory; private readonly IDialogService _dialogService; private readonly IPluginService _pluginService; private readonly ISettingsService _settingsService; - private readonly IPluginSettingsVmFactory _pluginSettingsVmFactory; + private readonly ISettingsVmFactory _settingsVmFactory; private readonly ISurfaceService _surfaceService; private List> _targetFrameRates; private List> _renderScales; @@ -35,9 +36,15 @@ namespace Artemis.UI.Screens.Settings private BindableCollection _deviceSettingsViewModels; private BindableCollection _plugins; - public SettingsViewModel(ISurfaceService surfaceService, IPluginService pluginService, IDialogService dialogService, IDebugService debugService, - ISettingsService settingsService, IPluginSettingsVmFactory pluginSettingsVmFactory, IDeviceSettingsVmFactory deviceSettingsVmFactory) + public SettingsViewModel(ISurfaceService surfaceService, + IPluginService pluginService, + IDialogService dialogService, + IDebugService debugService, + ISettingsService settingsService, + ISettingsVmFactory settingsVmFactory, + ModuleOrderViewModel moduleOrderViewModel) { + ModuleOrderViewModel = moduleOrderViewModel; DisplayName = "Settings"; _surfaceService = surfaceService; @@ -45,8 +52,7 @@ namespace Artemis.UI.Screens.Settings _dialogService = dialogService; _debugService = debugService; _settingsService = settingsService; - _pluginSettingsVmFactory = pluginSettingsVmFactory; - _deviceSettingsVmFactory = deviceSettingsVmFactory; + _settingsVmFactory = settingsVmFactory; DeviceSettingsViewModels = new BindableCollection(); Plugins = new BindableCollection(); @@ -225,10 +231,10 @@ namespace Artemis.UI.Screens.Settings Task.Run(ApplyAutorun); DeviceSettingsViewModels.Clear(); - DeviceSettingsViewModels.AddRange(_surfaceService.ActiveSurface.Devices.Select(d => _deviceSettingsVmFactory.Create(d))); + DeviceSettingsViewModels.AddRange(_surfaceService.ActiveSurface.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d))); Plugins.Clear(); - Plugins.AddRange(_pluginService.GetAllPluginInfo().Select(p => _pluginSettingsVmFactory.Create(p.Instance))); + Plugins.AddRange(_pluginService.GetAllPluginInfo().Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p.Instance))); base.OnInitialActivate(); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderView.xaml new file mode 100644 index 000000000..c37df4c17 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderView.xaml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + Overlays + + Modules that should always render on top + + + + + + + Plain + + + Old + + + ListBox + + + Full of junk + + + + + + + + + + + + + Applications/games + + Modules that are related to specific applications or games + + + + + + + Plain + + + Old + + + ListBox + + + Full of junk + + + + + + + + + + + + + Normal + + Regular modules that are always active in the background + + + + + + + Plain + + + Old + + + ListBox + + + Full of junk + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderViewModel.cs new file mode 100644 index 000000000..9391fd3ac --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderViewModel.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Tabs.Modules +{ + public class ModuleOrderViewModel : PropertyChangedBase + { + } +}