mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'development'
This commit is contained in:
commit
fb250ca405
@ -58,6 +58,7 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cmodules_005Cactivationrequirements/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cmodules_005Cactivationrequirements/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites_005Cprerequisiteaction/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites_005Cprerequisiteaction/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprofiling/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@ -62,8 +62,8 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
||||||
|
|
||||||
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin};
|
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
||||||
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin};
|
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||||
|
|
||||||
internal static JsonSerializerSettings JsonConvertSettings = new()
|
internal static JsonSerializerSettings JsonConvertSettings = new()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,40 +8,6 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class DataModelPluginFeature : PluginFeature
|
public abstract class DataModelPluginFeature : PluginFeature
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
|
|
||||||
/// provided
|
|
||||||
/// <paramref name="interval" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="interval">The interval at which the update should occur</param>
|
|
||||||
/// <param name="action">
|
|
||||||
/// The action to call every time the interval has passed. The delta time parameter represents the
|
|
||||||
/// time passed since the last update in seconds
|
|
||||||
/// </param>
|
|
||||||
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
|
|
||||||
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
|
|
||||||
{
|
|
||||||
if (action == null)
|
|
||||||
throw new ArgumentNullException(nameof(action));
|
|
||||||
return new TimedUpdateRegistration(this, interval, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="asyncAction" /> at the
|
|
||||||
/// provided
|
|
||||||
/// <paramref name="interval" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="interval">The interval at which the update should occur</param>
|
|
||||||
/// <param name="asyncAction">
|
|
||||||
/// The async action to call every time the interval has passed. The delta time parameter
|
|
||||||
/// represents the time passed since the last update in seconds
|
|
||||||
/// </param>
|
|
||||||
/// <returns>The resulting plugin update registration</returns>
|
|
||||||
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func<double, Task> asyncAction)
|
|
||||||
{
|
|
||||||
if (asyncAction == null)
|
|
||||||
throw new ArgumentNullException(nameof(asyncAction));
|
|
||||||
return new TimedUpdateRegistration(this, interval, asyncAction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,6 +205,7 @@ namespace Artemis.Core.Modules
|
|||||||
throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {profileEntity.Id} already in use.");
|
throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {profileEntity.Id} already in use.");
|
||||||
|
|
||||||
profileEntity.IsFreshImport = true;
|
profileEntity.IsFreshImport = true;
|
||||||
|
profileEntity.IsActive = false;
|
||||||
_defaultProfiles.Add(profileEntity);
|
_defaultProfiles.Add(profileEntity);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -17,6 +17,7 @@ namespace Artemis.Core
|
|||||||
public class Plugin : CorePropertyChanged, IDisposable
|
public class Plugin : CorePropertyChanged, IDisposable
|
||||||
{
|
{
|
||||||
private readonly List<PluginFeatureInfo> _features;
|
private readonly List<PluginFeatureInfo> _features;
|
||||||
|
private readonly List<Profiler> _profilers;
|
||||||
|
|
||||||
private bool _isEnabled;
|
private bool _isEnabled;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ namespace Artemis.Core
|
|||||||
Info.Plugin = this;
|
Info.Plugin = this;
|
||||||
|
|
||||||
_features = new List<PluginFeatureInfo>();
|
_features = new List<PluginFeatureInfo>();
|
||||||
|
_profilers = new List<Profiler>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -64,6 +66,8 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyCollection<PluginFeatureInfo> Features => _features.AsReadOnly();
|
public ReadOnlyCollection<PluginFeatureInfo> Features => _features.AsReadOnly();
|
||||||
|
|
||||||
|
public ReadOnlyCollection<Profiler> Profilers => _profilers.AsReadOnly();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The assembly the plugin code lives in
|
/// The assembly the plugin code lives in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -114,7 +118,7 @@ namespace Artemis.Core
|
|||||||
{
|
{
|
||||||
return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T;
|
return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up the feature info the feature of type <typeparamref name="T" />
|
/// Looks up the feature info the feature of type <typeparamref name="T" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -126,6 +130,31 @@ namespace Artemis.Core
|
|||||||
return _features.First(i => i.FeatureType == typeof(T));
|
return _features.First(i => i.FeatureType == typeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a profiler with the provided <paramref name="name" />, if it does not yet exist it will be created.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the profiler</param>
|
||||||
|
/// <returns>A new or existing profiler with the provided <paramref name="name" /></returns>
|
||||||
|
public Profiler GetProfiler(string name)
|
||||||
|
{
|
||||||
|
Profiler? profiler = _profilers.FirstOrDefault(p => p.Name == name);
|
||||||
|
if (profiler != null)
|
||||||
|
return profiler;
|
||||||
|
|
||||||
|
profiler = new Profiler(this, name);
|
||||||
|
_profilers.Add(profiler);
|
||||||
|
return profiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a profiler from the plugin
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profiler">The profiler to remove</param>
|
||||||
|
public void RemoveProfiler(Profiler profiler)
|
||||||
|
{
|
||||||
|
_profilers.Remove(profiler);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Storage.Entities.Plugins;
|
using Artemis.Storage.Entities.Plugins;
|
||||||
@ -12,11 +10,9 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PluginFeature : CorePropertyChanged, IDisposable
|
public abstract class PluginFeature : CorePropertyChanged, IDisposable
|
||||||
{
|
{
|
||||||
private readonly Stopwatch _renderStopwatch = new();
|
|
||||||
private readonly Stopwatch _updateStopwatch = new();
|
|
||||||
private bool _isEnabled;
|
private bool _isEnabled;
|
||||||
private Exception? _loadException;
|
private Exception? _loadException;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin feature info related to this feature
|
/// Gets the plugin feature info related to this feature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -27,6 +23,11 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Plugin Plugin { get; internal set; } = null!; // Will be set right after construction
|
public Plugin Plugin { get; internal set; } = null!; // Will be set right after construction
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profiler that can be used to take profiling measurements
|
||||||
|
/// </summary>
|
||||||
|
public Profiler Profiler { get; internal set; } = null!; // Will be set right after construction
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether the plugin is enabled
|
/// Gets whether the plugin is enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -50,16 +51,6 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Id => $"{GetType().FullName}-{Plugin.Guid.ToString().Substring(0, 8)}"; // Not as unique as a GUID but good enough and stays readable
|
public string Id => $"{GetType().FullName}-{Plugin.Guid.ToString().Substring(0, 8)}"; // Not as unique as a GUID but good enough and stays readable
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last measured update time of the feature
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan UpdateTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last measured render time of the feature
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan RenderTime { get; private set; }
|
|
||||||
|
|
||||||
internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction
|
internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -112,24 +103,22 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal void StartUpdateMeasure()
|
internal void StartUpdateMeasure()
|
||||||
{
|
{
|
||||||
_updateStopwatch.Start();
|
Profiler.StartMeasurement("Update");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void StopUpdateMeasure()
|
internal void StopUpdateMeasure()
|
||||||
{
|
{
|
||||||
UpdateTime = _updateStopwatch.Elapsed;
|
Profiler.StopMeasurement("Update");
|
||||||
_updateStopwatch.Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void StartRenderMeasure()
|
internal void StartRenderMeasure()
|
||||||
{
|
{
|
||||||
_renderStopwatch.Start();
|
Profiler.StartMeasurement("Render");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void StopRenderMeasure()
|
internal void StopRenderMeasure()
|
||||||
{
|
{
|
||||||
RenderTime = _renderStopwatch.Elapsed;
|
Profiler.StopMeasurement("Render");
|
||||||
_renderStopwatch.Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetEnabled(bool enable, bool isAutoEnable = false)
|
internal void SetEnabled(bool enable, bool isAutoEnable = false)
|
||||||
@ -242,5 +231,47 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Timed updates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
|
||||||
|
/// provided
|
||||||
|
/// <paramref name="interval" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="interval">The interval at which the update should occur</param>
|
||||||
|
/// <param name="action">
|
||||||
|
/// The action to call every time the interval has passed. The delta time parameter represents the
|
||||||
|
/// time passed since the last update in seconds
|
||||||
|
/// </param>
|
||||||
|
/// <param name="name">An optional name used in exceptions and profiling</param>
|
||||||
|
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
|
||||||
|
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action, string? name = null)
|
||||||
|
{
|
||||||
|
if (action == null)
|
||||||
|
throw new ArgumentNullException(nameof(action));
|
||||||
|
return new TimedUpdateRegistration(this, interval, action, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="asyncAction" /> at the
|
||||||
|
/// provided
|
||||||
|
/// <paramref name="interval" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="interval">The interval at which the update should occur</param>
|
||||||
|
/// <param name="asyncAction">
|
||||||
|
/// The async action to call every time the interval has passed. The delta time parameter
|
||||||
|
/// represents the time passed since the last update in seconds
|
||||||
|
/// </param>
|
||||||
|
/// <param name="name">An optional name used in exceptions and profiling</param>
|
||||||
|
/// <returns>The resulting plugin update registration</returns>
|
||||||
|
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func<double, Task> asyncAction, string? name = null)
|
||||||
|
{
|
||||||
|
if (asyncAction == null)
|
||||||
|
throw new ArgumentNullException(nameof(asyncAction));
|
||||||
|
return new TimedUpdateRegistration(this, interval, asyncAction, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,8 +58,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The plugins display icon that's shown in the settings see <see href="https://materialdesignicons.com" /> for
|
/// The plugins display icon that's shown in the settings see <see href="https://materialdesignicons.com" /> for
|
||||||
/// available
|
/// available icons
|
||||||
/// icons
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string? Icon
|
public string? Icon
|
||||||
@ -125,6 +124,8 @@ namespace Artemis.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
|
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
|
||||||
|
|
||||||
|
internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
84
src/Artemis.Core/Plugins/Profiling/Profiler.cs
Normal file
84
src/Artemis.Core/Plugins/Profiling/Profiler.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a profiler that can measure time between calls distinguished by identifiers
|
||||||
|
/// </summary>
|
||||||
|
public class Profiler
|
||||||
|
{
|
||||||
|
internal Profiler(Plugin plugin, string name)
|
||||||
|
{
|
||||||
|
Plugin = plugin;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the plugin this profiler belongs to
|
||||||
|
/// </summary>
|
||||||
|
public Plugin Plugin { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of this profiler
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary containing measurements by their identifiers
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, ProfilingMeasurement> Measurements { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts measuring time for the provided <paramref name="identifier" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">A unique identifier for this measurement</param>
|
||||||
|
public void StartMeasurement(string identifier)
|
||||||
|
{
|
||||||
|
lock (Measurements)
|
||||||
|
{
|
||||||
|
if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
|
||||||
|
{
|
||||||
|
measurement = new ProfilingMeasurement(identifier);
|
||||||
|
Measurements.Add(identifier, measurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
measurement.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops measuring time for the provided <paramref name="identifier" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">A unique identifier for this measurement</param>
|
||||||
|
/// <returns>The number of ticks that passed since the <see cref="StartMeasurement" /> call with the same identifier</returns>
|
||||||
|
public long StopMeasurement(string identifier)
|
||||||
|
{
|
||||||
|
long lockRequestedAt = Stopwatch.GetTimestamp();
|
||||||
|
lock (Measurements)
|
||||||
|
{
|
||||||
|
if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
|
||||||
|
{
|
||||||
|
measurement = new ProfilingMeasurement(identifier);
|
||||||
|
Measurements.Add(identifier, measurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return measurement.Stop(Stopwatch.GetTimestamp() - lockRequestedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears measurements with the the provided <paramref name="identifier" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier"></param>
|
||||||
|
public void ClearMeasurements(string identifier)
|
||||||
|
{
|
||||||
|
lock (Measurements)
|
||||||
|
{
|
||||||
|
Measurements.Remove(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
Normal file
141
src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a set of profiling measurements
|
||||||
|
/// </summary>
|
||||||
|
public class ProfilingMeasurement
|
||||||
|
{
|
||||||
|
private bool _filledArray;
|
||||||
|
private int _index;
|
||||||
|
private long _last;
|
||||||
|
private bool _open;
|
||||||
|
private long _start;
|
||||||
|
|
||||||
|
internal ProfilingMeasurement(string identifier)
|
||||||
|
{
|
||||||
|
Identifier = identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique identifier of this measurement
|
||||||
|
/// </summary>
|
||||||
|
public string Identifier { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last 1000 measurements
|
||||||
|
/// </summary>
|
||||||
|
public long[] Measurements { get; } = new long[1000];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts measuring time until <see cref="Stop" /> is called
|
||||||
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_start = Stopwatch.GetTimestamp();
|
||||||
|
_open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops measuring time and stores the time passed in the <see cref="Measurements" /> list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="correction">An optional correction in ticks to subtract from the measurement</param>
|
||||||
|
/// <returns>The time passed since the last <see cref="Start" /> call</returns>
|
||||||
|
public long Stop(long correction = 0)
|
||||||
|
{
|
||||||
|
if (!_open)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
long difference = Stopwatch.GetTimestamp() - _start - correction;
|
||||||
|
_open = false;
|
||||||
|
Measurements[_index] = difference;
|
||||||
|
|
||||||
|
_index++;
|
||||||
|
if (_index >= 1000)
|
||||||
|
{
|
||||||
|
_filledArray = true;
|
||||||
|
_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_last = difference;
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last measured time
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GetLast()
|
||||||
|
{
|
||||||
|
return new(_last);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the average time of the last 1000 measurements
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GetAverage()
|
||||||
|
{
|
||||||
|
if (!_filledArray && _index == 0)
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
|
||||||
|
return _filledArray
|
||||||
|
? new TimeSpan((long) Measurements.Average(m => m))
|
||||||
|
: new TimeSpan((long) Measurements.Take(_index).Average(m => m));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the min time of the last 1000 measurements
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GetMin()
|
||||||
|
{
|
||||||
|
if (!_filledArray && _index == 0)
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
|
||||||
|
return _filledArray
|
||||||
|
? new TimeSpan(Measurements.Min())
|
||||||
|
: new TimeSpan(Measurements.Take(_index).Min());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the max time of the last 1000 measurements
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GetMax()
|
||||||
|
{
|
||||||
|
if (!_filledArray && _index == 0)
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
|
||||||
|
return _filledArray
|
||||||
|
? new TimeSpan(Measurements.Max())
|
||||||
|
: new TimeSpan(Measurements.Take(_index).Max());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the nth percentile of the last 1000 measurements
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan GetPercentile(double percentile)
|
||||||
|
{
|
||||||
|
if (!_filledArray && _index == 0)
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
|
||||||
|
long[] collection = _filledArray
|
||||||
|
? Measurements.OrderBy(l => l).ToArray()
|
||||||
|
: Measurements.Take(_index).OrderBy(l => l).ToArray();
|
||||||
|
|
||||||
|
return new TimeSpan((long) Percentile(collection, percentile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double Percentile(long[] elements, double percentile)
|
||||||
|
{
|
||||||
|
Array.Sort(elements);
|
||||||
|
double realIndex = percentile * (elements.Length - 1);
|
||||||
|
int index = (int) realIndex;
|
||||||
|
double frac = realIndex - index;
|
||||||
|
if (index + 1 < elements.Length)
|
||||||
|
return elements[index] * (1 - frac) + elements[index + 1] * frac;
|
||||||
|
return elements[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Timers;
|
using System.Timers;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Humanizer;
|
||||||
using Ninject;
|
using Ninject;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -13,19 +14,20 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TimedUpdateRegistration : IDisposable
|
public class TimedUpdateRegistration : IDisposable
|
||||||
{
|
{
|
||||||
private DateTime _lastEvent;
|
|
||||||
private Timer? _timer;
|
|
||||||
private bool _disposed;
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
private ILogger _logger;
|
private bool _disposed;
|
||||||
|
private DateTime _lastEvent;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private Timer? _timer;
|
||||||
|
|
||||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action)
|
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action, string? name)
|
||||||
{
|
{
|
||||||
_logger = CoreService.Kernel.Get<ILogger>();
|
_logger = CoreService.Kernel.Get<ILogger>();
|
||||||
|
|
||||||
Feature = feature;
|
Feature = feature;
|
||||||
Interval = interval;
|
Interval = interval;
|
||||||
Action = action;
|
Action = action;
|
||||||
|
Name = name ?? $"TimedUpdate-{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||||
|
|
||||||
Feature.Enabled += FeatureOnEnabled;
|
Feature.Enabled += FeatureOnEnabled;
|
||||||
Feature.Disabled += FeatureOnDisabled;
|
Feature.Disabled += FeatureOnDisabled;
|
||||||
@ -33,13 +35,14 @@ namespace Artemis.Core
|
|||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction)
|
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction, string? name)
|
||||||
{
|
{
|
||||||
_logger = CoreService.Kernel.Get<ILogger>();
|
_logger = CoreService.Kernel.Get<ILogger>();
|
||||||
|
|
||||||
Feature = feature;
|
Feature = feature;
|
||||||
Interval = interval;
|
Interval = interval;
|
||||||
AsyncAction = asyncAction;
|
AsyncAction = asyncAction;
|
||||||
|
Name = name ?? $"TimedUpdate-{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||||
|
|
||||||
Feature.Enabled += FeatureOnEnabled;
|
Feature.Enabled += FeatureOnEnabled;
|
||||||
Feature.Disabled += FeatureOnDisabled;
|
Feature.Disabled += FeatureOnDisabled;
|
||||||
@ -69,7 +72,12 @@ namespace Artemis.Core
|
|||||||
public Func<double, Task>? AsyncAction { get; }
|
public Func<double, Task>? AsyncAction { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts calling the <see cref="Action" /> or <see cref="AsyncAction"/> at the configured <see cref="Interval" />
|
/// Gets the name of this timed update
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts calling the <see cref="Action" /> or <see cref="AsyncAction" /> at the configured <see cref="Interval" />
|
||||||
/// <para>Note: Called automatically when the plugin enables</para>
|
/// <para>Note: Called automatically when the plugin enables</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Start()
|
public void Start()
|
||||||
@ -93,7 +101,7 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops calling the <see cref="Action" /> or <see cref="AsyncAction"/> at the configured <see cref="Interval" />
|
/// Stops calling the <see cref="Action" /> or <see cref="AsyncAction" /> at the configured <see cref="Interval" />
|
||||||
/// <para>Note: Called automatically when the plugin disables</para>
|
/// <para>Note: Called automatically when the plugin disables</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
@ -113,49 +121,6 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
|
|
||||||
{
|
|
||||||
if (!Feature.IsEnabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
TimeSpan interval = DateTime.Now - _lastEvent;
|
|
||||||
_lastEvent = DateTime.Now;
|
|
||||||
|
|
||||||
// Modules don't always want to update, honor that
|
|
||||||
if (Feature is Module module && !module.IsUpdateAllowed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Action != null)
|
|
||||||
Action(interval.TotalSeconds);
|
|
||||||
else if (AsyncAction != null)
|
|
||||||
{
|
|
||||||
Task task = AsyncAction(interval.TotalSeconds);
|
|
||||||
task.Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.Error(exception, "Timed update uncaught exception in plugin {plugin}", Feature.Plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FeatureOnEnabled(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FeatureOnDisabled(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -176,6 +141,55 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!Feature.IsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Feature.Profiler.StartMeasurement(ToString());
|
||||||
|
|
||||||
|
TimeSpan interval = DateTime.Now - _lastEvent;
|
||||||
|
_lastEvent = DateTime.Now;
|
||||||
|
|
||||||
|
// Modules don't always want to update, honor that
|
||||||
|
if (Feature is Module module && !module.IsUpdateAllowed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Action != null)
|
||||||
|
{
|
||||||
|
Action(interval.TotalSeconds);
|
||||||
|
}
|
||||||
|
else if (AsyncAction != null)
|
||||||
|
{
|
||||||
|
Task task = AsyncAction(interval.TotalSeconds);
|
||||||
|
task.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error(exception, "{timedUpdate} uncaught exception in plugin {plugin}", this, Feature.Plugin);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Feature.Profiler.StopMeasurement(ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FeatureOnEnabled(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FeatureOnDisabled(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@ -183,6 +197,12 @@ namespace Artemis.Core
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <inheritdoc />
|
||||||
|
public sealed override string ToString()
|
||||||
|
{
|
||||||
|
if (Interval.TotalSeconds >= 1)
|
||||||
|
return $"{Name} ({Interval.TotalSeconds} sec)";
|
||||||
|
return $"{Name} ({Interval.TotalMilliseconds} ms)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,9 +39,9 @@ namespace Artemis.Core.Services
|
|||||||
ProcessQueuedActions();
|
ProcessQueuedActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyBuiltInPlugin(FileInfo zipFileInfo, ZipArchive zipArchive)
|
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
||||||
{
|
{
|
||||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins", Path.GetFileNameWithoutExtension(zipFileInfo.Name)));
|
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins", targetDirectory));
|
||||||
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
||||||
|
|
||||||
// Remove the old directory if it exists
|
// Remove the old directory if it exists
|
||||||
@ -81,12 +81,18 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
using StreamReader reader = new(metaDataFileEntry.Open());
|
||||||
PluginInfo builtInPluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
PluginInfo builtInPluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
||||||
|
string preferred = builtInPluginInfo.PreferredPluginDirectory;
|
||||||
|
string oldPreferred = Path.GetFileNameWithoutExtension(zipFile.Name);
|
||||||
|
// Rename folders to the new format
|
||||||
|
// TODO: Get rid of this eventually, it's nice to keep around but it's extra IO that's best avoided
|
||||||
|
if (pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == oldPreferred) != null)
|
||||||
|
Directory.Move(Path.Combine(pluginDirectory.FullName, oldPreferred), Path.Combine(pluginDirectory.FullName, preferred));
|
||||||
|
|
||||||
// Find the matching plugin in the plugin folder
|
// Find the matching plugin in the plugin folder
|
||||||
DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == Path.GetFileNameWithoutExtension(zipFile.Name));
|
DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == preferred);
|
||||||
if (match == null)
|
if (match == null)
|
||||||
{
|
{
|
||||||
CopyBuiltInPlugin(zipFile, archive);
|
CopyBuiltInPlugin(archive, preferred);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -94,7 +100,7 @@ namespace Artemis.Core.Services
|
|||||||
if (!File.Exists(metadataFile))
|
if (!File.Exists(metadataFile))
|
||||||
{
|
{
|
||||||
_logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo);
|
_logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo);
|
||||||
CopyBuiltInPlugin(zipFile, archive);
|
CopyBuiltInPlugin(archive, preferred);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -114,7 +120,7 @@ namespace Artemis.Core.Services
|
|||||||
if (builtInPluginInfo.Version > pluginInfo.Version)
|
if (builtInPluginInfo.Version > pluginInfo.Version)
|
||||||
{
|
{
|
||||||
_logger.Debug("Copying updated built-in plugin from {pluginInfo} to {builtInPluginInfo}", pluginInfo, builtInPluginInfo);
|
_logger.Debug("Copying updated built-in plugin from {pluginInfo} to {builtInPluginInfo}", pluginInfo, builtInPluginInfo);
|
||||||
CopyBuiltInPlugin(zipFile, archive);
|
CopyBuiltInPlugin(archive, preferred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -342,8 +348,8 @@ namespace Artemis.Core.Services
|
|||||||
foreach (Type featureType in featureTypes)
|
foreach (Type featureType in featureTypes)
|
||||||
{
|
{
|
||||||
// Load the enabled state and if not found, default to true
|
// Load the enabled state and if not found, default to true
|
||||||
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
|
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
|
||||||
new PluginFeatureEntity { IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureType.FullName! };
|
new PluginFeatureEntity {IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureType.FullName!};
|
||||||
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute))));
|
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,6 +417,7 @@ namespace Artemis.Core.Services
|
|||||||
featureInfo.Instance = instance;
|
featureInfo.Instance = instance;
|
||||||
instance.Info = featureInfo;
|
instance.Info = featureInfo;
|
||||||
instance.Plugin = plugin;
|
instance.Plugin = plugin;
|
||||||
|
instance.Profiler = plugin.GetProfiler("Feature - " + featureInfo.Name);
|
||||||
instance.Entity = featureInfo.Entity;
|
instance.Entity = featureInfo.Entity;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -501,6 +508,7 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RemovePlugin(existing, false);
|
RemovePlugin(existing, false);
|
||||||
@ -509,20 +517,14 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e);
|
throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", "");
|
|
||||||
string uniqueTargetDirectory = targetDirectory;
|
|
||||||
int attempt = 2;
|
|
||||||
|
|
||||||
// Find a unique folder
|
|
||||||
while (pluginDirectory.EnumerateDirectories().Any(d => d.Name == uniqueTargetDirectory))
|
|
||||||
{
|
|
||||||
uniqueTargetDirectory = targetDirectory + "-" + attempt;
|
|
||||||
attempt++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string targetDirectory = pluginInfo.PreferredPluginDirectory;
|
||||||
|
if (Directory.Exists(Path.Combine(pluginDirectory.FullName, targetDirectory)))
|
||||||
|
throw new ArtemisPluginException($"A directory for this plugin already exists {Path.Combine(pluginDirectory.FullName, targetDirectory)}");
|
||||||
|
|
||||||
// Extract everything in the same archive directory to the unique plugin directory
|
// Extract everything in the same archive directory to the unique plugin directory
|
||||||
DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory));
|
DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, targetDirectory));
|
||||||
Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
|
Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
|
||||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
||||||
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
|
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
|
||||||
|
|||||||
@ -311,8 +311,9 @@ namespace Artemis.Core.Services
|
|||||||
// Assign a new GUID to make sure it is unique in case of a previous import of the same content
|
// Assign a new GUID to make sure it is unique in case of a previous import of the same content
|
||||||
profileEntity.UpdateGuid(Guid.NewGuid());
|
profileEntity.UpdateGuid(Guid.NewGuid());
|
||||||
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
||||||
|
|
||||||
profileEntity.IsFreshImport = true;
|
profileEntity.IsFreshImport = true;
|
||||||
|
profileEntity.IsActive = false;
|
||||||
|
|
||||||
_profileRepository.Add(profileEntity);
|
_profileRepository.Add(profileEntity);
|
||||||
return new ProfileDescriptor(profileModule, profileEntity);
|
return new ProfileDescriptor(profileModule, profileEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -372,5 +372,8 @@
|
|||||||
<Page Update="Screens\ProfileEditor\Dialogs\ProfileEditView.xaml">
|
<Page Update="Screens\ProfileEditor\Dialogs\ProfileEditView.xaml">
|
||||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Update="Screens\Settings\Debug\Tabs\Performance\PerformanceDebugView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
</Page>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -71,7 +71,7 @@ namespace Artemis.UI.Providers
|
|||||||
Execute.OnUIThreadSync(() =>
|
Execute.OnUIThreadSync(() =>
|
||||||
{
|
{
|
||||||
using FileStream stream = File.OpenWrite(imagePath);
|
using FileStream stream = File.OpenWrite(imagePath);
|
||||||
GetEncoderForIcon(icon, _themeWatcher.GetWindowsTheme() == ThemeWatcher.WindowsTheme.Dark ? Colors.White : Colors.Black).Save(stream);
|
GetEncoderForIcon(icon, _themeWatcher.GetSystemTheme() == ThemeWatcher.WindowsTheme.Dark ? Colors.White : Colors.Black).Save(stream);
|
||||||
});
|
});
|
||||||
|
|
||||||
new ToastContentBuilder()
|
new ToastContentBuilder()
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
@ -107,6 +108,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
{
|
{
|
||||||
RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified;
|
RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified;
|
||||||
RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified;
|
RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified;
|
||||||
|
RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderProfileElement = e.RenderProfileElement;
|
RenderProfileElement = e.RenderProfileElement;
|
||||||
@ -134,6 +136,14 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
|||||||
|
|
||||||
RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified;
|
RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified;
|
||||||
RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified;
|
RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified;
|
||||||
|
RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimelineOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
NotifyOfPropertyChange(nameof(DisplayContinuously));
|
||||||
|
NotifyOfPropertyChange(nameof(AlwaysFinishTimeline));
|
||||||
|
NotifyOfPropertyChange(nameof(EventOverlapMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e)
|
private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e)
|
||||||
|
|||||||
@ -190,6 +190,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
|
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
|
||||||
ShowDisableButton = SegmentWidth > 25;
|
ShowDisableButton = SegmentWidth > 25;
|
||||||
|
|
||||||
|
if (Segment == SegmentViewModelType.Main)
|
||||||
|
NotifyOfPropertyChange(nameof(RepeatSegment));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Screens.Settings.Debug.Tabs;
|
using Artemis.UI.Screens.Settings.Debug.Tabs;
|
||||||
|
using Artemis.UI.Screens.Settings.Debug.Tabs.Performance;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Settings.Debug
|
namespace Artemis.UI.Screens.Settings.Debug
|
||||||
@ -16,12 +16,14 @@ namespace Artemis.UI.Screens.Settings.Debug
|
|||||||
ICoreService coreService,
|
ICoreService coreService,
|
||||||
RenderDebugViewModel renderDebugViewModel,
|
RenderDebugViewModel renderDebugViewModel,
|
||||||
DataModelDebugViewModel dataModelDebugViewModel,
|
DataModelDebugViewModel dataModelDebugViewModel,
|
||||||
LogsDebugViewModel logsDebugViewModel)
|
LogsDebugViewModel logsDebugViewModel,
|
||||||
|
PerformanceDebugViewModel performanceDebugViewModel)
|
||||||
{
|
{
|
||||||
_coreService = coreService;
|
_coreService = coreService;
|
||||||
Items.Add(renderDebugViewModel);
|
Items.Add(renderDebugViewModel);
|
||||||
Items.Add(dataModelDebugViewModel);
|
Items.Add(dataModelDebugViewModel);
|
||||||
Items.Add(logsDebugViewModel);
|
Items.Add(logsDebugViewModel);
|
||||||
|
Items.Add(performanceDebugViewModel);
|
||||||
ActiveItem = renderDebugViewModel;
|
ActiveItem = renderDebugViewModel;
|
||||||
|
|
||||||
StayOnTopSetting = settingsService.GetSetting("Debugger.StayOnTop", false);
|
StayOnTopSetting = settingsService.GetSetting("Debugger.StayOnTop", false);
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||||
|
{
|
||||||
|
public class PerformanceDebugMeasurementViewModel : PropertyChangedBase
|
||||||
|
{
|
||||||
|
private string _average;
|
||||||
|
private string _last;
|
||||||
|
private string _max;
|
||||||
|
private string _min;
|
||||||
|
private string _percentile;
|
||||||
|
|
||||||
|
public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement)
|
||||||
|
{
|
||||||
|
Measurement = measurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfilingMeasurement Measurement { get; }
|
||||||
|
|
||||||
|
public string Last
|
||||||
|
{
|
||||||
|
get => _last;
|
||||||
|
set => SetAndNotify(ref _last, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Average
|
||||||
|
{
|
||||||
|
get => _average;
|
||||||
|
set => SetAndNotify(ref _average, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Min
|
||||||
|
{
|
||||||
|
get => _min;
|
||||||
|
set => SetAndNotify(ref _min, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Max
|
||||||
|
{
|
||||||
|
get => _max;
|
||||||
|
set => SetAndNotify(ref _max, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Percentile
|
||||||
|
{
|
||||||
|
get => _percentile;
|
||||||
|
set => SetAndNotify(ref _percentile, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
Last = Measurement.GetLast().TotalMilliseconds + " ms";
|
||||||
|
Average = Measurement.GetAverage().TotalMilliseconds + " ms";
|
||||||
|
Min = Measurement.GetMin().TotalMilliseconds + " ms";
|
||||||
|
Max = Measurement.GetMax().TotalMilliseconds + " ms";
|
||||||
|
Percentile = Measurement.GetPercentile(0.95).TotalMilliseconds + " ms";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.Performance.PerformanceDebugPluginView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs.Performance"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance local:PerformanceDebugPluginViewModel}">
|
||||||
|
<materialDesign:Card Margin="0 5" Padding="10">
|
||||||
|
<StackPanel>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="40"/>
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<shared:ArtemisIcon Grid.Column="0" Icon="{Binding Icon}" Width="24" Height="24" />
|
||||||
|
<TextBlock Grid.Column="1" VerticalAlignment="Center" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Text="{Binding Plugin.Info.Name}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding Profilers}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</materialDesign:Card>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||||
|
{
|
||||||
|
public class PerformanceDebugPluginViewModel : Screen
|
||||||
|
{
|
||||||
|
public Plugin Plugin { get; }
|
||||||
|
public object Icon { get; }
|
||||||
|
|
||||||
|
public PerformanceDebugPluginViewModel(Plugin plugin)
|
||||||
|
{
|
||||||
|
Plugin = plugin;
|
||||||
|
Icon = PluginUtilities.GetPluginIcon(Plugin, Plugin.Info.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindableCollection<PerformanceDebugProfilerViewModel> Profilers { get; } = new();
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
foreach (Profiler pluginProfiler in Plugin.Profilers.Where(p => p.Measurements.Any()))
|
||||||
|
{
|
||||||
|
if (Profilers.All(p => p.Profiler != pluginProfiler))
|
||||||
|
Profilers.Add(new PerformanceDebugProfilerViewModel(pluginProfiler));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (PerformanceDebugProfilerViewModel profilerViewModel in Profilers)
|
||||||
|
profilerViewModel.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.Performance.PerformanceDebugProfilerView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs.Performance"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance local:PerformanceDebugProfilerViewModel}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Profiler.Name}" Margin="10 10 0 0" />
|
||||||
|
|
||||||
|
<DataGrid ItemsSource="{Binding Measurements}"
|
||||||
|
d:DataContext="{d:DesignInstance Type={x:Type local:PerformanceDebugMeasurementViewModel}}"
|
||||||
|
CanUserSortColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
materialDesign:DataGridAssist.CellPadding="16 5 5 5"
|
||||||
|
materialDesign:DataGridAssist.ColumnHeaderPadding="16 5 5 5"
|
||||||
|
CanUserResizeRows="False"
|
||||||
|
Margin="10 5 10 10">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<materialDesign:DataGridTextColumn Binding="{Binding Measurement.Identifier}" Header="Identifier" />
|
||||||
|
<materialDesign:DataGridTextColumn Binding="{Binding Last}" Header="Last" />
|
||||||
|
<materialDesign:DataGridTextColumn Binding="{Binding Min}" Header="Min" />
|
||||||
|
<materialDesign:DataGridTextColumn Binding="{Binding Max}" Header="Max" />
|
||||||
|
<materialDesign:DataGridTextColumn Binding="{Binding Average}" Header="Average" />
|
||||||
|
<materialDesign:DataGridTextColumn Binding="{Binding Percentile}" Header="95th percentile" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||||
|
{
|
||||||
|
public class PerformanceDebugProfilerViewModel : Screen
|
||||||
|
{
|
||||||
|
public Profiler Profiler { get; }
|
||||||
|
|
||||||
|
public PerformanceDebugProfilerViewModel(Profiler profiler)
|
||||||
|
{
|
||||||
|
Profiler = profiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindableCollection<PerformanceDebugMeasurementViewModel> Measurements { get; } = new();
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
foreach ((string _, ProfilingMeasurement measurement) in Profiler.Measurements)
|
||||||
|
{
|
||||||
|
if (Measurements.All(m => m.Measurement != measurement))
|
||||||
|
Measurements.Add(new PerformanceDebugMeasurementViewModel(measurement));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (PerformanceDebugMeasurementViewModel profilingMeasurementViewModel in Measurements)
|
||||||
|
profilingMeasurementViewModel.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.Performance.PerformanceDebugView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs.Performance"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance local:PerformanceDebugViewModel}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Grid.Row="0" TextWrapping="Wrap">
|
||||||
|
In this window you can see how much CPU time different plugin features are taking.
|
||||||
|
If you are having performance issues, below you can find out which plugin might be the culprit.
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
|
||||||
|
<ItemsControl ItemsSource="{Binding Items}" Margin="0 0 10 0">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<materialDesign:TransitioningContent OpeningEffect="{materialDesign:TransitionEffect SlideInFromLeft}"
|
||||||
|
OpeningEffectsOffset="{materialDesign:IndexedItemOffsetMultiplier 0:0:0.05}">
|
||||||
|
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
||||||
|
</materialDesign:TransitioningContent>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for PerformanceDebugView.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class PerformanceDebugView : UserControl
|
||||||
|
{
|
||||||
|
public PerformanceDebugView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||||
|
{
|
||||||
|
ScrollViewer scv = (ScrollViewer)sender;
|
||||||
|
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Timers;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||||
|
{
|
||||||
|
public class PerformanceDebugViewModel : Conductor<PerformanceDebugPluginViewModel>.Collection.AllActive
|
||||||
|
{
|
||||||
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
private readonly Timer _updateTimer;
|
||||||
|
|
||||||
|
public PerformanceDebugViewModel(IPluginManagementService pluginManagementService)
|
||||||
|
{
|
||||||
|
_pluginManagementService = pluginManagementService;
|
||||||
|
_updateTimer = new Timer(500);
|
||||||
|
|
||||||
|
DisplayName = "PERFORMANCE";
|
||||||
|
_updateTimer.Elapsed += UpdateTimerOnElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (PerformanceDebugPluginViewModel viewModel in Items)
|
||||||
|
viewModel.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FeatureToggled(object? sender, PluginFeatureEventArgs e)
|
||||||
|
{
|
||||||
|
Items.Clear();
|
||||||
|
PopulateItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginToggled(object? sender, PluginEventArgs e)
|
||||||
|
{
|
||||||
|
Items.Clear();
|
||||||
|
PopulateItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateItems()
|
||||||
|
{
|
||||||
|
Items.AddRange(_pluginManagementService.GetAllPlugins()
|
||||||
|
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
|
||||||
|
.OrderBy(p => p.Info.Name)
|
||||||
|
.Select(p => new PerformanceDebugPluginViewModel(p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Screen
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnActivate()
|
||||||
|
{
|
||||||
|
PopulateItems();
|
||||||
|
_updateTimer.Start();
|
||||||
|
_pluginManagementService.PluginDisabled += PluginToggled;
|
||||||
|
_pluginManagementService.PluginDisabled += PluginToggled;
|
||||||
|
_pluginManagementService.PluginFeatureEnabled += FeatureToggled;
|
||||||
|
_pluginManagementService.PluginFeatureDisabled += FeatureToggled;
|
||||||
|
base.OnActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnDeactivate()
|
||||||
|
{
|
||||||
|
_updateTimer.Stop();
|
||||||
|
_pluginManagementService.PluginDisabled -= PluginToggled;
|
||||||
|
_pluginManagementService.PluginDisabled -= PluginToggled;
|
||||||
|
_pluginManagementService.PluginFeatureEnabled -= FeatureToggled;
|
||||||
|
_pluginManagementService.PluginFeatureDisabled -= FeatureToggled;
|
||||||
|
Items.Clear();
|
||||||
|
base.OnDeactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,8 +10,7 @@
|
|||||||
<tb:TaskbarIcon IconSource="{Binding Icon}"
|
<tb:TaskbarIcon IconSource="{Binding Icon}"
|
||||||
MenuActivation="LeftOrRightClick"
|
MenuActivation="LeftOrRightClick"
|
||||||
PopupActivation="DoubleClick"
|
PopupActivation="DoubleClick"
|
||||||
DoubleClickCommand="{s:Action TrayBringToForeground}"
|
DoubleClickCommand="{s:Action TrayBringToForeground}">
|
||||||
TrayBalloonTipClicked="{s:Action OnTrayBalloonTipClicked}">
|
|
||||||
|
|
||||||
<tb:TaskbarIcon.TrayToolTip>
|
<tb:TaskbarIcon.TrayToolTip>
|
||||||
<Border Background="{DynamicResource MaterialDesignToolTipBackground}" CornerRadius="2" Padding="5">
|
<Border Background="{DynamicResource MaterialDesignToolTipBackground}" CornerRadius="2" Padding="5">
|
||||||
|
|||||||
@ -26,10 +26,11 @@ namespace Artemis.UI.Screens
|
|||||||
private readonly IKernel _kernel;
|
private readonly IKernel _kernel;
|
||||||
private readonly ThemeWatcher _themeWatcher;
|
private readonly ThemeWatcher _themeWatcher;
|
||||||
private readonly IWindowManager _windowManager;
|
private readonly IWindowManager _windowManager;
|
||||||
|
private ImageSource _icon;
|
||||||
|
private bool _openingMainWindow;
|
||||||
private RootViewModel _rootViewModel;
|
private RootViewModel _rootViewModel;
|
||||||
private SplashViewModel _splashViewModel;
|
private SplashViewModel _splashViewModel;
|
||||||
private TaskbarIcon _taskBarIcon;
|
private TaskbarIcon _taskBarIcon;
|
||||||
private ImageSource _icon;
|
|
||||||
|
|
||||||
public TrayViewModel(IKernel kernel,
|
public TrayViewModel(IKernel kernel,
|
||||||
IWindowManager windowManager,
|
IWindowManager windowManager,
|
||||||
@ -51,9 +52,11 @@ namespace Artemis.UI.Screens
|
|||||||
_themeWatcher = new ThemeWatcher();
|
_themeWatcher = new ThemeWatcher();
|
||||||
_colorScheme = settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic);
|
_colorScheme = settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic);
|
||||||
_colorScheme.SettingChanged += ColorSchemeOnSettingChanged;
|
_colorScheme.SettingChanged += ColorSchemeOnSettingChanged;
|
||||||
_themeWatcher.ThemeChanged += ThemeWatcherOnThemeChanged;
|
_themeWatcher.SystemThemeChanged += _themeWatcher_SystemThemeChanged;
|
||||||
|
_themeWatcher.AppsThemeChanged += _themeWatcher_AppsThemeChanged;
|
||||||
|
|
||||||
ApplyColorSchemeSetting();
|
ApplyColorSchemeSetting();
|
||||||
|
ApplyTrayIconTheme(_themeWatcher.GetSystemTheme());
|
||||||
|
|
||||||
windowService.ConfigureMainWindowProvider(this);
|
windowService.ConfigureMainWindowProvider(this);
|
||||||
bool autoRunning = Bootstrapper.StartupArguments.Contains("--autorun");
|
bool autoRunning = Bootstrapper.StartupArguments.Contains("--autorun");
|
||||||
@ -61,7 +64,9 @@ namespace Artemis.UI.Screens
|
|||||||
bool showOnAutoRun = settingsService.GetSetting("UI.ShowOnStartup", true).Value;
|
bool showOnAutoRun = settingsService.GetSetting("UI.ShowOnStartup", true).Value;
|
||||||
|
|
||||||
if (autoRunning && !showOnAutoRun || minimized)
|
if (autoRunning && !showOnAutoRun || minimized)
|
||||||
|
{
|
||||||
coreService.Initialized += (_, _) => updateService.AutoUpdate();
|
coreService.Initialized += (_, _) => updateService.AutoUpdate();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ShowSplashScreen();
|
ShowSplashScreen();
|
||||||
@ -69,36 +74,55 @@ namespace Artemis.UI.Screens
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TrayBringToForeground()
|
|
||||||
{
|
|
||||||
if (IsMainWindowOpen)
|
|
||||||
{
|
|
||||||
Execute.PostToUIThread(FocusMainWindow);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the shared UI when first showing the window
|
|
||||||
if (!UI.Shared.Bootstrapper.Initialized)
|
|
||||||
UI.Shared.Bootstrapper.Initialize(_kernel);
|
|
||||||
|
|
||||||
Execute.OnUIThreadSync(() =>
|
|
||||||
{
|
|
||||||
_splashViewModel?.RequestClose();
|
|
||||||
_splashViewModel = null;
|
|
||||||
_rootViewModel = _kernel.Get<RootViewModel>();
|
|
||||||
_rootViewModel.Closed += RootViewModelOnClosed;
|
|
||||||
_windowManager.ShowWindow(_rootViewModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
OnMainWindowOpened();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageSource Icon
|
public ImageSource Icon
|
||||||
{
|
{
|
||||||
get => _icon;
|
get => _icon;
|
||||||
set => SetAndNotify(ref _icon, value);
|
set => SetAndNotify(ref _icon, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TrayBringToForeground()
|
||||||
|
{
|
||||||
|
if (_openingMainWindow)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_openingMainWindow = true;
|
||||||
|
|
||||||
|
if (IsMainWindowOpen)
|
||||||
|
{
|
||||||
|
Execute.OnUIThreadSync(() =>
|
||||||
|
{
|
||||||
|
FocusMainWindow();
|
||||||
|
_openingMainWindow = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the shared UI when first showing the window
|
||||||
|
if (!UI.Shared.Bootstrapper.Initialized)
|
||||||
|
UI.Shared.Bootstrapper.Initialize(_kernel);
|
||||||
|
|
||||||
|
Execute.OnUIThreadSync(() =>
|
||||||
|
{
|
||||||
|
_splashViewModel?.RequestClose();
|
||||||
|
_splashViewModel = null;
|
||||||
|
_rootViewModel = _kernel.Get<RootViewModel>();
|
||||||
|
_rootViewModel.Closed += RootViewModelOnClosed;
|
||||||
|
_windowManager.ShowWindow(_rootViewModel);
|
||||||
|
|
||||||
|
IsMainWindowOpen = true;
|
||||||
|
_openingMainWindow = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
OnMainWindowOpened();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_openingMainWindow = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void TrayActivateSidebarItem(string sidebarItem)
|
public void TrayActivateSidebarItem(string sidebarItem)
|
||||||
{
|
{
|
||||||
TrayBringToForeground();
|
TrayBringToForeground();
|
||||||
@ -120,14 +144,6 @@ namespace Artemis.UI.Screens
|
|||||||
_taskBarIcon = (TaskbarIcon) ((ContentControl) view).Content;
|
_taskBarIcon = (TaskbarIcon) ((ContentControl) view).Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTrayBalloonTipClicked(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (!IsMainWindowOpen)
|
|
||||||
TrayBringToForeground();
|
|
||||||
else
|
|
||||||
FocusMainWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FocusMainWindow()
|
private void FocusMainWindow()
|
||||||
{
|
{
|
||||||
// Wrestle the main window to the front
|
// Wrestle the main window to the front
|
||||||
@ -156,10 +172,15 @@ namespace Artemis.UI.Screens
|
|||||||
|
|
||||||
private void RootViewModelOnClosed(object sender, CloseEventArgs e)
|
private void RootViewModelOnClosed(object sender, CloseEventArgs e)
|
||||||
{
|
{
|
||||||
if (_rootViewModel != null)
|
lock (this)
|
||||||
{
|
{
|
||||||
_rootViewModel.Closed -= RootViewModelOnClosed;
|
if (_rootViewModel != null)
|
||||||
_rootViewModel = null;
|
{
|
||||||
|
_rootViewModel.Closed -= RootViewModelOnClosed;
|
||||||
|
_rootViewModel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsMainWindowOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnMainWindowClosed();
|
OnMainWindowClosed();
|
||||||
@ -170,28 +191,31 @@ namespace Artemis.UI.Screens
|
|||||||
private void ApplyColorSchemeSetting()
|
private void ApplyColorSchemeSetting()
|
||||||
{
|
{
|
||||||
if (_colorScheme.Value == ApplicationColorScheme.Automatic)
|
if (_colorScheme.Value == ApplicationColorScheme.Automatic)
|
||||||
ApplyWindowsTheme(_themeWatcher.GetWindowsTheme());
|
ApplyUITheme(_themeWatcher.GetAppsTheme());
|
||||||
else
|
else
|
||||||
ChangeMaterialColors(_colorScheme.Value);
|
ChangeMaterialColors(_colorScheme.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyWindowsTheme(ThemeWatcher.WindowsTheme windowsTheme)
|
private void ApplyUITheme(ThemeWatcher.WindowsTheme theme)
|
||||||
{
|
{
|
||||||
Execute.PostToUIThread(() =>
|
|
||||||
{
|
|
||||||
Icon = windowsTheme == ThemeWatcher.WindowsTheme.Dark
|
|
||||||
? new BitmapImage(new Uri("pack://application:,,,/Artemis.UI;component/Resources/Images/Logo/bow-white.ico"))
|
|
||||||
: new BitmapImage(new Uri("pack://application:,,,/Artemis.UI;component/Resources/Images/Logo/bow-black.ico"));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_colorScheme.Value != ApplicationColorScheme.Automatic)
|
if (_colorScheme.Value != ApplicationColorScheme.Automatic)
|
||||||
return;
|
return;
|
||||||
if (windowsTheme == ThemeWatcher.WindowsTheme.Dark)
|
if (theme == ThemeWatcher.WindowsTheme.Dark)
|
||||||
ChangeMaterialColors(ApplicationColorScheme.Dark);
|
ChangeMaterialColors(ApplicationColorScheme.Dark);
|
||||||
else
|
else
|
||||||
ChangeMaterialColors(ApplicationColorScheme.Light);
|
ChangeMaterialColors(ApplicationColorScheme.Light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyTrayIconTheme(ThemeWatcher.WindowsTheme theme)
|
||||||
|
{
|
||||||
|
Execute.PostToUIThread(() =>
|
||||||
|
{
|
||||||
|
Icon = theme == ThemeWatcher.WindowsTheme.Dark
|
||||||
|
? new BitmapImage(new Uri("pack://application:,,,/Artemis.UI;component/Resources/Images/Logo/bow-white.ico"))
|
||||||
|
: new BitmapImage(new Uri("pack://application:,,,/Artemis.UI;component/Resources/Images/Logo/bow-black.ico"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void ChangeMaterialColors(ApplicationColorScheme colorScheme)
|
private void ChangeMaterialColors(ApplicationColorScheme colorScheme)
|
||||||
{
|
{
|
||||||
PaletteHelper paletteHelper = new();
|
PaletteHelper paletteHelper = new();
|
||||||
@ -203,9 +227,14 @@ namespace Artemis.UI.Screens
|
|||||||
extensionsPaletteHelper.SetLightDark(colorScheme == ApplicationColorScheme.Dark);
|
extensionsPaletteHelper.SetLightDark(colorScheme == ApplicationColorScheme.Dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThemeWatcherOnThemeChanged(object sender, WindowsThemeEventArgs e)
|
private void _themeWatcher_AppsThemeChanged(object sender, WindowsThemeEventArgs e)
|
||||||
{
|
{
|
||||||
ApplyWindowsTheme(e.Theme);
|
ApplyUITheme(e.Theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _themeWatcher_SystemThemeChanged(object sender, WindowsThemeEventArgs e)
|
||||||
|
{
|
||||||
|
ApplyTrayIconTheme(e.Theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorSchemeOnSettingChanged(object sender, EventArgs e)
|
private void ColorSchemeOnSettingChanged(object sender, EventArgs e)
|
||||||
@ -221,10 +250,7 @@ namespace Artemis.UI.Screens
|
|||||||
|
|
||||||
public bool OpenMainWindow()
|
public bool OpenMainWindow()
|
||||||
{
|
{
|
||||||
if (IsMainWindowOpen)
|
TrayBringToForeground();
|
||||||
Execute.OnUIThread(FocusMainWindow);
|
|
||||||
else
|
|
||||||
TrayBringToForeground();
|
|
||||||
return _rootViewModel.ScreenState == ScreenState.Active;
|
return _rootViewModel.ScreenState == ScreenState.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,13 +266,11 @@ namespace Artemis.UI.Screens
|
|||||||
|
|
||||||
protected virtual void OnMainWindowOpened()
|
protected virtual void OnMainWindowOpened()
|
||||||
{
|
{
|
||||||
IsMainWindowOpen = true;
|
|
||||||
MainWindowOpened?.Invoke(this, EventArgs.Empty);
|
MainWindowOpened?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnMainWindowClosed()
|
protected virtual void OnMainWindowClosed()
|
||||||
{
|
{
|
||||||
IsMainWindowOpen = false;
|
|
||||||
MainWindowClosed?.Invoke(this, EventArgs.Empty);
|
MainWindowClosed?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,8 @@ namespace Artemis.UI.Utilities
|
|||||||
{
|
{
|
||||||
private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
||||||
|
|
||||||
private const string RegistryValueName = "AppsUseLightTheme";
|
private const string appsThemeRegistryValueName = "AppsUseLightTheme";
|
||||||
|
private const string systemThemeRegistryValueName = "SystemUsesLightTheme";
|
||||||
|
|
||||||
public ThemeWatcher()
|
public ThemeWatcher()
|
||||||
{
|
{
|
||||||
@ -21,24 +22,44 @@ namespace Artemis.UI.Utilities
|
|||||||
public void WatchTheme()
|
public void WatchTheme()
|
||||||
{
|
{
|
||||||
WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
|
WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
|
||||||
string query = string.Format(
|
string appsThemequery = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'",
|
@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'",
|
||||||
currentUser.User.Value,
|
currentUser.User.Value,
|
||||||
RegistryKeyPath.Replace(@"\", @"\\"),
|
RegistryKeyPath.Replace(@"\", @"\\"),
|
||||||
RegistryValueName);
|
appsThemeRegistryValueName);
|
||||||
|
|
||||||
|
string systemThemequery = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'",
|
||||||
|
currentUser.User.Value,
|
||||||
|
RegistryKeyPath.Replace(@"\", @"\\"),
|
||||||
|
systemThemeRegistryValueName);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ManagementEventWatcher watcher = new(query);
|
// For Apps theme
|
||||||
watcher.EventArrived += (sender, args) =>
|
ManagementEventWatcher appsThemWatcher = new(appsThemequery);
|
||||||
|
appsThemWatcher.EventArrived += (_, _) =>
|
||||||
{
|
{
|
||||||
WindowsTheme newWindowsTheme = GetWindowsTheme();
|
WindowsTheme newWindowsTheme = GetAppsTheme();
|
||||||
OnThemeChanged(new WindowsThemeEventArgs(newWindowsTheme));
|
OnAppsThemeChanged(new WindowsThemeEventArgs(newWindowsTheme));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start listening for events
|
// Start listening for apps theme events
|
||||||
watcher.Start();
|
appsThemWatcher.Start();
|
||||||
|
|
||||||
|
|
||||||
|
// For System theme
|
||||||
|
ManagementEventWatcher systemThemWatcher = new(systemThemequery);
|
||||||
|
systemThemWatcher.EventArrived += (_, _) =>
|
||||||
|
{
|
||||||
|
WindowsTheme newWindowsTheme = GetSystemTheme();
|
||||||
|
OnSystemThemeChanged(new WindowsThemeEventArgs(newWindowsTheme));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start listening for system theme events
|
||||||
|
systemThemWatcher.Start();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@ -46,25 +67,40 @@ namespace Artemis.UI.Utilities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public WindowsTheme GetWindowsTheme()
|
private WindowsTheme GetTheme(string themeKeyName)
|
||||||
{
|
{
|
||||||
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath))
|
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath))
|
||||||
{
|
{
|
||||||
object registryValueObject = key?.GetValue(RegistryValueName);
|
object registryValueObject = key?.GetValue(themeKeyName);
|
||||||
if (registryValueObject == null) return WindowsTheme.Light;
|
if (registryValueObject == null) return WindowsTheme.Light;
|
||||||
|
|
||||||
int registryValue = (int) registryValueObject;
|
int registryValue = (int)registryValueObject;
|
||||||
|
|
||||||
return registryValue > 0 ? WindowsTheme.Light : WindowsTheme.Dark;
|
return registryValue > 0 ? WindowsTheme.Light : WindowsTheme.Dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<WindowsThemeEventArgs> ThemeChanged;
|
public WindowsTheme GetAppsTheme()
|
||||||
|
|
||||||
|
|
||||||
protected virtual void OnThemeChanged(WindowsThemeEventArgs e)
|
|
||||||
{
|
{
|
||||||
ThemeChanged?.Invoke(this, e);
|
return GetTheme(appsThemeRegistryValueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowsTheme GetSystemTheme()
|
||||||
|
{
|
||||||
|
return GetTheme(systemThemeRegistryValueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<WindowsThemeEventArgs> AppsThemeChanged;
|
||||||
|
public event EventHandler<WindowsThemeEventArgs> SystemThemeChanged;
|
||||||
|
|
||||||
|
protected virtual void OnAppsThemeChanged(WindowsThemeEventArgs e)
|
||||||
|
{
|
||||||
|
AppsThemeChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnSystemThemeChanged(WindowsThemeEventArgs e)
|
||||||
|
{
|
||||||
|
SystemThemeChanged?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WindowsTheme
|
public enum WindowsTheme
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user