From 796c0dc671c0130cd8a4d6b18b060da4165747a8 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 29 Jun 2020 00:22:16 +0200 Subject: [PATCH] Data model - Added data model visualization view model Debugger - Split debugger into different tabs Debugger - Added data model debugger --- .../Extensions/DirectoryInfoExtensions.cs | 4 +- src/Artemis.Core/Extensions/TypeExtensions.cs | 5 ++ .../Abstract/BaseDataModelExpansion.cs | 17 +++- .../Plugins/Abstract/DataModels/DataModel.cs | 2 + src/Artemis.Core/Plugins/Abstract/Module.cs | 21 +++-- src/Artemis.Core/Plugins/Abstract/Plugin.cs | 46 +++++++--- .../Plugins/Abstract/ProfileModule.cs | 18 +++- .../Exceptions/ArtemisPluginLockException.cs | 18 ++++ src/Artemis.Core/Plugins/Models/PluginInfo.cs | 38 +++++--- src/Artemis.Core/Services/CoreService.cs | 5 +- src/Artemis.Core/Services/DataModelService.cs | 45 +++++----- .../Services/Interfaces/IPluginService.cs | 3 +- src/Artemis.Core/Services/PluginService.cs | 41 ++++----- src/Artemis.Storage/Artemis.Storage.csproj | 1 + .../Entities/Plugins/PluginEntity.cs | 4 +- .../Screens/Dialogs/ExceptionDialogView.xaml | 7 +- .../DataModelGroupViewModel.cs | 6 +- .../DataModelPropertyViewModel.cs | 32 +++++-- .../DataModelViewModel.cs | 36 +++++--- .../Screens/Settings/Debug/DebugView.xaml | 41 +++------ .../Screens/Settings/Debug/DebugViewModel.cs | 82 ++---------------- .../Debug/Tabs/DataModelDebugView.xaml | 49 +++++++++++ .../Debug/Tabs/DataModelDebugViewModel.cs | 35 ++++++++ .../Settings/Debug/Tabs/LogsDebugView.xaml | 12 +++ .../Settings/Debug/Tabs/LogsDebugView.xaml.cs | 26 ++++++ .../Settings/Debug/Tabs/LogsDebugViewModel.cs | 15 ++++ .../Settings/Debug/Tabs/RenderDebugView.xaml | 35 ++++++++ .../Debug/Tabs/RenderDebugViewModel.cs | 86 +++++++++++++++++++ .../Tabs/Plugins/PluginSettingsView.xaml | 24 +++--- .../Tabs/Plugins/PluginSettingsViewModel.cs | 26 +++--- .../Services/DataModelVisualizationService.cs | 2 +- .../GeneralDataModel.cs | 10 +++ .../GeneralModule.cs | 9 ++ 33 files changed, 565 insertions(+), 236 deletions(-) create mode 100644 src/Artemis.Core/Plugins/Exceptions/ArtemisPluginLockException.cs create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugView.xaml.cs create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugViewModel.cs create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml create mode 100644 src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs diff --git a/src/Artemis.Core/Extensions/DirectoryInfoExtensions.cs b/src/Artemis.Core/Extensions/DirectoryInfoExtensions.cs index afd1df921..641e938e8 100644 --- a/src/Artemis.Core/Extensions/DirectoryInfoExtensions.cs +++ b/src/Artemis.Core/Extensions/DirectoryInfoExtensions.cs @@ -12,13 +12,13 @@ namespace Artemis.Core.Extensions file.CopyTo(Path.Combine(target.FullName, file.Name)); } - public static void RecursiveDelete(this DirectoryInfo baseDir) + public static void DeleteRecursively(this DirectoryInfo baseDir) { if (!baseDir.Exists) return; foreach (var dir in baseDir.EnumerateDirectories()) - RecursiveDelete(dir); + DeleteRecursively(dir); var files = baseDir.GetFiles(); foreach (var file in files) { diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs index 78069713e..79d3e7027 100644 --- a/src/Artemis.Core/Extensions/TypeExtensions.cs +++ b/src/Artemis.Core/Extensions/TypeExtensions.cs @@ -12,6 +12,11 @@ namespace Artemis.Core.Extensions return type.BaseType?.GetGenericTypeDefinition() == genericType; } + public static bool IsStruct(this Type source) + { + return source.IsValueType && !source.IsPrimitive && !source.IsEnum; + } + public static bool IsNumber(this object value) { return value is sbyte diff --git a/src/Artemis.Core/Plugins/Abstract/BaseDataModelExpansion.cs b/src/Artemis.Core/Plugins/Abstract/BaseDataModelExpansion.cs index 396e0d84a..28847607b 100644 --- a/src/Artemis.Core/Plugins/Abstract/BaseDataModelExpansion.cs +++ b/src/Artemis.Core/Plugins/Abstract/BaseDataModelExpansion.cs @@ -1,4 +1,5 @@ -using Artemis.Core.Plugins.Abstract.DataModels; +using System; +using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; namespace Artemis.Core.Plugins.Abstract @@ -16,6 +17,20 @@ namespace Artemis.Core.Plugins.Abstract get => (T) InternalDataModel; internal set => InternalDataModel = value; } + + internal override void InternalEnablePlugin() + { + DataModel = Activator.CreateInstance(); + DataModel.PluginInfo = PluginInfo; + DataModel.DataModelDescription = GetDataModelDescription(); + base.InternalEnablePlugin(); + } + + internal override void InternalDisablePlugin() + { + DataModel = null; + base.InternalDisablePlugin(); + } } /// diff --git a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs index 7e72eeaec..0c8882e9a 100644 --- a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs +++ b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs @@ -8,11 +8,13 @@ namespace Artemis.Core.Plugins.Abstract.DataModels /// /// Gets the plugin info this data model belongs to /// + [DataModelIgnore] public PluginInfo PluginInfo { get; internal set; } /// /// Gets the describing this data model /// + [DataModelIgnore] public DataModelPropertyAttribute DataModelDescription { get; internal set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/Module.cs b/src/Artemis.Core/Plugins/Abstract/Module.cs index 5ebce30f6..6c2e2bcab 100644 --- a/src/Artemis.Core/Plugins/Abstract/Module.cs +++ b/src/Artemis.Core/Plugins/Abstract/Module.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; @@ -44,9 +45,18 @@ namespace Artemis.Core.Plugins.Abstract return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description}; } - internal override DataModelPropertyAttribute InternalGetDataModelDescription() + internal override void InternalEnablePlugin() { - return GetDataModelDescription(); + DataModel = Activator.CreateInstance(); + DataModel.PluginInfo = PluginInfo; + DataModel.DataModelDescription = GetDataModelDescription(); + base.InternalEnablePlugin(); + } + + internal override void InternalDisablePlugin() + { + DataModel = null; + base.InternalDisablePlugin(); } } @@ -91,10 +101,5 @@ namespace Artemis.Core.Plugins.Abstract /// /// public abstract IEnumerable GetViewModels(); - - internal virtual DataModelPropertyAttribute InternalGetDataModelDescription() - { - return null; - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/Plugin.cs b/src/Artemis.Core/Plugins/Abstract/Plugin.cs index 702675d42..71197abfb 100644 --- a/src/Artemis.Core/Plugins/Abstract/Plugin.cs +++ b/src/Artemis.Core/Plugins/Abstract/Plugin.cs @@ -1,6 +1,8 @@ using System; using Artemis.Core.Plugins.Abstract.ViewModels; +using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Plugins.Models; +using Castle.Core.Internal; namespace Artemis.Core.Plugins.Abstract { @@ -9,7 +11,7 @@ namespace Artemis.Core.Plugins.Abstract /// public abstract class Plugin : IDisposable { - public PluginInfo PluginInfo { get; internal set; } + public PluginInfo PluginInfo { get; internal set; } /// /// Gets whether the plugin is enabled @@ -47,26 +49,39 @@ namespace Artemis.Core.Plugins.Abstract return null; } - internal void SetEnabled(bool enable) + internal void SetEnabled(bool enable, bool isAutoEnable = false) { if (enable && !Enabled) { - Enabled = true; - PluginInfo.Enabled = true; - - // If enable failed, put it back in a disabled state try { - EnablePlugin(); + if (isAutoEnable && PluginInfo.GetLockFileCreated()) + { + // Don't wrap existing lock exceptions, simply rethrow them + if (PluginInfo.LoadException is ArtemisPluginLockException) + throw PluginInfo.LoadException; + + throw new ArtemisPluginLockException(PluginInfo.LoadException); + } + + Enabled = true; + PluginInfo.Enabled = true; + PluginInfo.CreateLockFile(); + + InternalEnablePlugin(); + OnPluginEnabled(); + PluginInfo.LoadException = null; } - catch + // If enable failed, put it back in a disabled state + catch (Exception e) { Enabled = false; PluginInfo.Enabled = false; + PluginInfo.LoadException = e; throw; } - OnPluginEnabled(); + PluginInfo.DeleteLockFile(); } else if (!enable && Enabled) { @@ -74,12 +89,21 @@ namespace Artemis.Core.Plugins.Abstract PluginInfo.Enabled = false; // Even if disable failed, still leave it in a disabled state to avoid more issues - DisablePlugin(); - + InternalDisablePlugin(); OnPluginDisabled(); } } + internal virtual void InternalEnablePlugin() + { + EnablePlugin(); + } + + internal virtual void InternalDisablePlugin() + { + DisablePlugin(); + } + #region Events public event EventHandler PluginEnabled; diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 8120e1bd6..8c3582854 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -4,6 +4,7 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.Core.Plugins.Models; using SkiaSharp; namespace Artemis.Core.Plugins.Abstract @@ -18,7 +19,7 @@ namespace Artemis.Core.Plugins.Abstract /// public T DataModel { - get => (T)InternalDataModel; + get => (T) InternalDataModel; internal set => InternalDataModel = value; } @@ -42,12 +43,21 @@ namespace Artemis.Core.Plugins.Abstract /// public virtual DataModelPropertyAttribute GetDataModelDescription() { - return new DataModelPropertyAttribute { Name = PluginInfo.Name, Description = PluginInfo.Description }; + return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description}; + } + + internal override void InternalEnablePlugin() + { + DataModel = Activator.CreateInstance(); + DataModel.PluginInfo = PluginInfo; + DataModel.DataModelDescription = GetDataModelDescription(); + base.InternalEnablePlugin(); } - internal override DataModelPropertyAttribute InternalGetDataModelDescription() + internal override void InternalDisablePlugin() { - return GetDataModelDescription(); + DataModel = null; + base.InternalDisablePlugin(); } } diff --git a/src/Artemis.Core/Plugins/Exceptions/ArtemisPluginLockException.cs b/src/Artemis.Core/Plugins/Exceptions/ArtemisPluginLockException.cs new file mode 100644 index 000000000..3fa5c4674 --- /dev/null +++ b/src/Artemis.Core/Plugins/Exceptions/ArtemisPluginLockException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Artemis.Core.Plugins.Exceptions +{ + public class ArtemisPluginLockException : Exception + { + public ArtemisPluginLockException(Exception innerException) : base(CreateExceptionMessage(innerException), innerException) + { + } + + private static string CreateExceptionMessage(Exception innerException) + { + return innerException != null + ? "Found a lock file, skipping load, see inner exception for last known exception." + : "Found a lock file, skipping load."; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Models/PluginInfo.cs b/src/Artemis.Core/Plugins/Models/PluginInfo.cs index ebc61b150..3ff3676d5 100644 --- a/src/Artemis.Core/Plugins/Models/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/Models/PluginInfo.cs @@ -12,16 +12,17 @@ namespace Artemis.Core.Plugins.Models [JsonObject(MemberSerialization.OptIn)] public class PluginInfo : PropertyChangedBase { - private Guid _guid; - private string _name; private string _description; - private string _icon; - private Version _version; - private string _main; private DirectoryInfo _directory; - private Plugin _instance; private bool _enabled; + private Guid _guid; + private string _icon; + private Plugin _instance; private bool _lastEnableSuccessful; + private Exception _loadException; + private string _main; + private string _name; + private Version _version; internal PluginInfo() { @@ -117,12 +118,12 @@ namespace Artemis.Core.Plugins.Models } /// - /// Indicates whether the last time the plugin loaded, it loaded correctly + /// Gets the exception thrown while loading /// - public bool LastEnableSuccessful + public Exception LoadException { - get => _lastEnableSuccessful; - internal set => SetAndNotify(ref _lastEnableSuccessful, value); + get => _loadException; + internal set => SetAndNotify(ref _loadException, value); } /// @@ -149,7 +150,22 @@ namespace Artemis.Core.Plugins.Models { PluginEntity.Id = Guid; PluginEntity.IsEnabled = Enabled; - PluginEntity.LastEnableSuccessful = LastEnableSuccessful; + } + + internal void CreateLockFile() + { + File.Create(Path.Combine(Directory.FullName, "artemis.lock")).Close(); + } + + internal void DeleteLockFile() + { + if (GetLockFileCreated()) + File.Delete(Path.Combine(Directory.FullName, "artemis.lock")); + } + + internal bool GetLockFileCreated() + { + return File.Exists(Path.Combine(Directory.FullName, "artemis.lock")); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index c47593056..899a49c42 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection; using Artemis.Core.Events; using Artemis.Core.Exceptions; @@ -107,8 +108,8 @@ namespace Artemis.Core.Services private void UpdatePluginCache() { - _modules = _pluginService.GetPluginsOfType(); - _dataModelExpansions = _pluginService.GetPluginsOfType(); + _modules = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); + _dataModelExpansions = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); } private void ConfigureJsonConvert() diff --git a/src/Artemis.Core/Services/DataModelService.cs b/src/Artemis.Core/Services/DataModelService.cs index e5cdd5273..b5f323a57 100644 --- a/src/Artemis.Core/Services/DataModelService.cs +++ b/src/Artemis.Core/Services/DataModelService.cs @@ -3,10 +3,8 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Events; using Artemis.Core.Exceptions; -using Artemis.Core.Models; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.DataModels; -using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; @@ -17,8 +15,8 @@ namespace Artemis.Core.Services /// public class DataModelService : IDataModelService { - private readonly IPluginService _pluginService; private readonly List _dataModelExpansions; + private readonly IPluginService _pluginService; internal DataModelService(IPluginService pluginService) { @@ -27,6 +25,11 @@ namespace Artemis.Core.Services _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; + + foreach (var module in _pluginService.GetPluginsOfType().Where(m => m.InternalExpandsMainDataModel)) + AddModuleDataModel(module); + foreach (var dataModelExpansion in _pluginService.GetPluginsOfType()) + AddDataModelExpansionDataModel(dataModelExpansion); } public ReadOnlyCollection DataModelExpansions @@ -64,27 +67,25 @@ namespace Artemis.Core.Services private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) { if (e.PluginInfo.Instance is Module module && module.InternalExpandsMainDataModel) - { - if (module.InternalDataModel.DataModelDescription == null) - { - module.InternalDataModel.DataModelDescription = module.InternalGetDataModelDescription(); - if (module.InternalDataModel.DataModelDescription == null) - throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null"); - } - - _dataModelExpansions.Add(module.InternalDataModel); - } + AddModuleDataModel(module); else if (e.PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion) - { - if (dataModelExpansion.InternalDataModel.DataModelDescription == null) - { - dataModelExpansion.InternalDataModel.DataModelDescription = dataModelExpansion.GetDataModelDescription(); - if (dataModelExpansion.InternalDataModel.DataModelDescription == null) - throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null"); - } + AddDataModelExpansionDataModel(dataModelExpansion); + } - _dataModelExpansions.Add(dataModelExpansion.InternalDataModel); - } + private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion) + { + if (dataModelExpansion.InternalDataModel.DataModelDescription == null) + throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null"); + + AddExpansion(dataModelExpansion.InternalDataModel); + } + + private void AddModuleDataModel(Module module) + { + if (module.InternalDataModel.DataModelDescription == null) + throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null"); + + AddExpansion(module.InternalDataModel); } private void PluginServiceOnPluginDisabled(object sender, PluginEventArgs e) diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index f55ad9de6..931e31f95 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -46,7 +46,8 @@ namespace Artemis.Core.Services.Interfaces /// Enables the provided plugin /// /// - void EnablePlugin(Plugin plugin); + /// If true, fails if there is a lock file present + void EnablePlugin(Plugin plugin, bool isAutoEnable = false); /// /// Disables the provided plugin diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index cfe0a5649..8c5565862 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -148,22 +148,14 @@ namespace Artemis.Core.Services // Activate plugins after they are all loaded foreach (var pluginInfo in _plugins.Where(p => p.Enabled)) { - if (!pluginInfo.LastEnableSuccessful) - { - pluginInfo.Enabled = false; - _logger.Warning("Plugin failed to load last time, disabling it now to avoid instability. Plugin info: {pluginInfo}", pluginInfo); - continue; - } - try { - EnablePlugin(pluginInfo.Instance); + EnablePlugin(pluginInfo.Instance, true); } catch (Exception) { // ignored, logged in EnablePlugin } - } LoadingPlugins = false; @@ -204,11 +196,10 @@ namespace Artemis.Core.Services var pluginEntity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid); if (pluginEntity == null) - pluginEntity = new PluginEntity {Id = pluginInfo.Guid, IsEnabled = true, LastEnableSuccessful = true}; + pluginEntity = new PluginEntity {Id = pluginInfo.Guid, IsEnabled = true}; pluginInfo.PluginEntity = pluginEntity; pluginInfo.Enabled = pluginEntity.IsEnabled; - pluginInfo.LastEnableSuccessful = pluginEntity.LastEnableSuccessful; var mainFile = Path.Combine(pluginInfo.Directory.FullName, pluginInfo.Main); if (!File.Exists(mainFile)) @@ -294,20 +285,15 @@ namespace Artemis.Core.Services } } - public void EnablePlugin(Plugin plugin) + public void EnablePlugin(Plugin plugin, bool isAutoEnable = false) { lock (_plugins) { _logger.Debug("Enabling plugin {pluginInfo}", plugin.PluginInfo); - plugin.PluginInfo.LastEnableSuccessful = false; - plugin.PluginInfo.ApplyToEntity(); - - _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); - try { - plugin.SetEnabled(true); + plugin.SetEnabled(true, isAutoEnable); } catch (Exception e) { @@ -316,16 +302,16 @@ namespace Artemis.Core.Services } finally { - // We got this far so the plugin enabled and we didn't crash horribly, yay - if (plugin.PluginInfo.Enabled) - { - plugin.PluginInfo.LastEnableSuccessful = true; - plugin.PluginInfo.ApplyToEntity(); + // On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does + // not affect the user's settings + if (isAutoEnable) + plugin.PluginInfo.Enabled = true; - _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); + plugin.PluginInfo.ApplyToEntity(); + _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); + if (plugin.PluginInfo.Enabled) _logger.Debug("Successfully enabled plugin {pluginInfo}", plugin.PluginInfo); - } } } @@ -398,13 +384,16 @@ namespace Artemis.Core.Services private static void CopyBuiltInPlugin(DirectoryInfo builtInPluginDirectory) { var pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins", builtInPluginDirectory.Name)); + var createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock")); // Remove the old directory if it exists if (Directory.Exists(pluginDirectory.FullName)) - pluginDirectory.RecursiveDelete(); + pluginDirectory.DeleteRecursively(); Directory.CreateDirectory(pluginDirectory.FullName); builtInPluginDirectory.CopyFilesRecursively(pluginDirectory); + if (createLockFile) + File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close(); } #region Events diff --git a/src/Artemis.Storage/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj index 0ae5807f4..e4e212bb0 100644 --- a/src/Artemis.Storage/Artemis.Storage.csproj +++ b/src/Artemis.Storage/Artemis.Storage.csproj @@ -6,6 +6,7 @@ + \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs index ae59c9769..b81a93152 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs @@ -1,4 +1,6 @@ using System; +using LiteDB; +using Newtonsoft.Json; namespace Artemis.Storage.Entities.Plugins { @@ -8,8 +10,6 @@ namespace Artemis.Storage.Entities.Plugins public class PluginEntity { public Guid Id { get; set; } - public bool IsEnabled { get; set; } - public bool LastEnableSuccessful { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Screens/Dialogs/ExceptionDialogView.xaml b/src/Artemis.UI.Shared/Screens/Dialogs/ExceptionDialogView.xaml index 95ed127ab..7c32e5251 100644 --- a/src/Artemis.UI.Shared/Screens/Dialogs/ExceptionDialogView.xaml +++ b/src/Artemis.UI.Shared/Screens/Dialogs/ExceptionDialogView.xaml @@ -20,8 +20,11 @@ - + : DataModelPropertyViewModel { - private readonly ObjectAccessor _accessor; + private readonly Func _expression; public DataModelPropertyViewModel(PropertyInfo propertyInfo, DataModelPropertyAttribute propertyDescription, DataModelViewModel parent) { - _accessor = ObjectAccessor.Create(parent.Model); - PropertyInfo = propertyInfo; Parent = parent; PropertyDescription = propertyDescription; + + var instance = Expression.Parameter(typeof(T), "instance"); + var body = Expression.Property(instance, propertyInfo); + _expression = Expression.Lambda>(body, instance).Compile(); } - public PropertyInfo PropertyInfo { get; } - public object Value => _accessor[PropertyInfo.Name]; + public TP Value + { + get => BaseValue is TP value ? value : default; + set => BaseValue = value; + } + + public override void Update() + { + Value = _expression((T) Parent.Model); + } + } + + public abstract class DataModelPropertyViewModel : DataModelVisualizationViewModel + { + public object BaseValue { get; protected set; } } } \ No newline at end of file diff --git a/src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs b/src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs index c38c4fd73..997c3cd29 100644 --- a/src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs +++ b/src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs @@ -1,4 +1,6 @@ using System; +using System.Reflection; +using Artemis.Core.Extensions; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Humanizer; using Stylet; @@ -12,8 +14,9 @@ namespace Artemis.UI.DataModelVisualization Children = new BindableCollection(); } - public DataModelViewModel(object model, DataModelPropertyAttribute propertyDescription, DataModelViewModel parent) + public DataModelViewModel(PropertyInfo propertyInfo, object model, DataModelPropertyAttribute propertyDescription, DataModelViewModel parent) { + PropertyInfo = propertyInfo; Model = model; PropertyDescription = propertyDescription; Parent = parent; @@ -22,7 +25,7 @@ namespace Artemis.UI.DataModelVisualization PopulateProperties(); } - public object Model { get; } + public object Model { get; private set; } public BindableCollection Children { get; set; } public void PopulateProperties() @@ -39,21 +42,34 @@ namespace Artemis.UI.DataModelVisualization if (dataModelPropertyAttribute == null) dataModelPropertyAttribute = new DataModelPropertyAttribute {Name = propertyInfo.Name.Humanize()}; - // For value types create a child view model if the value type is not null - if (propertyInfo.PropertyType.IsValueType) + // For primitives, create a property view model, it may be null that is fine + if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType == typeof(string)) + { + // This may be slower than avoiding generics and Activator.CreateInstance but it allows for expression trees inside the VM we're creating + // here, this means slow creation but fast updates after that + var viewModelType = typeof(DataModelPropertyViewModel<,>).MakeGenericType(Model.GetType(), propertyInfo.PropertyType); + var viewModel = (DataModelVisualizationViewModel) Activator.CreateInstance(viewModelType, propertyInfo, dataModelPropertyAttribute, this); + Children.Add(viewModel); + } + // For other value types create a child view model if the value type is not null + else if (propertyInfo.PropertyType.IsClass || propertyInfo.PropertyType.IsStruct()) { var value = propertyInfo.GetValue(Model); if (value == null) continue; - Children.Add(new DataModelViewModel(value, dataModelPropertyAttribute, this)); - } - // For primitives, create a property view model, it may be null that is fine - else if (propertyInfo.PropertyType.IsPrimitive) - { - Children.Add(new DataModelPropertyViewModel(propertyInfo, dataModelPropertyAttribute, this)); + Children.Add(new DataModelViewModel(propertyInfo, value, dataModelPropertyAttribute, this)); } } } + + public override void Update() + { + if (PropertyInfo != null && PropertyInfo.PropertyType.IsStruct()) + Model = PropertyInfo.GetValue(Parent.Model); + + foreach (var dataModelVisualizationViewModel in Children) + dataModelVisualizationViewModel.Update(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml index c139f7f6e..8f7a8371a 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml @@ -27,39 +27,26 @@ - + - - diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index d4d90bd48..674495900 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -32,23 +32,18 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public Plugin Plugin { get; set; } public PluginInfo PluginInfo { get; set; } - - public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; - + public bool Enabling { get; set; } public PackIconKind Icon => GetIconKind(); + public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; + public bool CanOpenSettings => IsEnabled && Plugin.HasConfigurationViewModel; + public bool DisplayLoadFailed => !Enabling && PluginInfo.LoadException != null; public bool IsEnabled { - get => PluginInfo.Enabled; + get => Plugin.Enabled; set => Task.Run(() => UpdateEnabled(value)); } - public bool CanOpenSettings => IsEnabled && Plugin.HasConfigurationViewModel; - - public bool Enabling { get; set; } - - public bool DisplayLoadFailed => !Enabling && !PluginInfo.LastEnableSuccessful; - public async Task OpenSettings() { try @@ -76,6 +71,14 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } } + public async Task ShowLoadException() + { + if (PluginInfo.LoadException == null) + return; + + await _dialogService.ShowExceptionDialog("The plugin failed to load: " + PluginInfo.LoadException.Message, PluginInfo.LoadException); + } + private PackIconKind GetIconKind() { if (PluginInfo.Icon != null) @@ -106,7 +109,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private async Task UpdateEnabled(bool enable) { - if (PluginInfo.Enabled == enable) + if (Plugin.Enabled == enable) { NotifyOfPropertyChange(nameof(IsEnabled)); return; @@ -133,7 +136,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins try { _pluginService.EnablePlugin(Plugin); - _snackbarMessageQueue.Enqueue($"Enabled plugin {PluginInfo.Name}"); } catch (Exception e) { diff --git a/src/Artemis.UI/Services/DataModelVisualizationService.cs b/src/Artemis.UI/Services/DataModelVisualizationService.cs index 703a98505..73570da85 100644 --- a/src/Artemis.UI/Services/DataModelVisualizationService.cs +++ b/src/Artemis.UI/Services/DataModelVisualizationService.cs @@ -17,7 +17,7 @@ namespace Artemis.UI.Services { var viewModel = new DataModelViewModel(); foreach (var dataModelExpansion in _dataModelService.DataModelExpansions) - viewModel.Children.Add(new DataModelViewModel(dataModelExpansion, dataModelExpansion.DataModelDescription, viewModel)); + viewModel.Children.Add(new DataModelViewModel(null,dataModelExpansion, dataModelExpansion.DataModelDescription, viewModel)); return viewModel; } diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs index e7d18cef1..1b849b988 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs @@ -1,10 +1,16 @@ using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using SkiaSharp; namespace Artemis.Plugins.Modules.General { public class GeneralDataModel : DataModel { + public GeneralDataModel() + { + PlayerInfo = new PlayerInfo(); + } + [DataModelProperty(Name = "A test string", Description = "This is a test string that's not of any use outside testing!")] public string TestString { get; set; } @@ -13,6 +19,8 @@ namespace Artemis.Plugins.Modules.General [DataModelProperty(Name = "Player info", Description = "[TEST] Contains information about the player")] public PlayerInfo PlayerInfo { get; set; } + + public double UpdatesDividedByFour { get; set; } } public class PlayerInfo : DataModel @@ -22,5 +30,7 @@ namespace Artemis.Plugins.Modules.General [DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")] public bool TestBoolean { get; set; } + + public SKPoint Position { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index 72209225e..2651738aa 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -5,16 +5,19 @@ using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.ViewModels; using Artemis.Core.Plugins.Models; using Artemis.Plugins.Modules.General.ViewModels; +using SkiaSharp; namespace Artemis.Plugins.Modules.General { public class GeneralModule : ProfileModule { private readonly PluginSettings _settings; + private Random _rand; public GeneralModule(PluginSettings settings) { _settings = settings; + _rand = new Random(); } public override IEnumerable GetViewModels() @@ -22,6 +25,12 @@ namespace Artemis.Plugins.Modules.General return new List {new GeneralViewModel(this)}; } + public override void Update(double deltaTime) + { + DataModel.UpdatesDividedByFour += 0.25; + DataModel.PlayerInfo.Position = new SKPoint(_rand.Next(100), _rand.Next(100)); + } + public override void EnablePlugin() { DisplayName = "General";