diff --git a/src/Artemis.Core/Plugins/DataModelPluginFeature.cs b/src/Artemis.Core/Plugins/DataModelPluginFeature.cs
index 83649847a..099fba242 100644
--- a/src/Artemis.Core/Plugins/DataModelPluginFeature.cs
+++ b/src/Artemis.Core/Plugins/DataModelPluginFeature.cs
@@ -8,40 +8,6 @@ namespace Artemis.Core
///
public abstract class DataModelPluginFeature : PluginFeature
{
- ///
- /// Registers a timed update that whenever the plugin is enabled calls the provided at the
- /// provided
- ///
- ///
- /// The interval at which the update should occur
- ///
- /// The action to call every time the interval has passed. The delta time parameter represents the
- /// time passed since the last update in seconds
- ///
- /// The resulting plugin update registration which can be used to stop the update
- public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action action)
- {
- if (action == null)
- throw new ArgumentNullException(nameof(action));
- return new TimedUpdateRegistration(this, interval, action);
- }
-
- ///
- /// Registers a timed update that whenever the plugin is enabled calls the provided at the
- /// provided
- ///
- ///
- /// The interval at which the update should occur
- ///
- /// 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
- ///
- /// The resulting plugin update registration
- public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func asyncAction)
- {
- if (asyncAction == null)
- throw new ArgumentNullException(nameof(asyncAction));
- return new TimedUpdateRegistration(this, interval, asyncAction);
- }
+
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs
index 4d226640a..bb6235ac0 100644
--- a/src/Artemis.Core/Plugins/Plugin.cs
+++ b/src/Artemis.Core/Plugins/Plugin.cs
@@ -146,6 +146,15 @@ namespace Artemis.Core
return profiler;
}
+ ///
+ /// Removes a profiler from the plugin
+ ///
+ /// The profiler to remove
+ public void RemoveProfiler(Profiler profiler)
+ {
+ _profilers.Remove(profiler);
+ }
+
///
public override string ToString()
{
diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs
index 2fd931ebf..983f20438 100644
--- a/src/Artemis.Core/Plugins/PluginFeature.cs
+++ b/src/Artemis.Core/Plugins/PluginFeature.cs
@@ -51,16 +51,6 @@ namespace Artemis.Core
///
public string Id => $"{GetType().FullName}-{Plugin.Guid.ToString().Substring(0, 8)}"; // Not as unique as a GUID but good enough and stays readable
- ///
- /// Gets the last measured update time of the feature
- ///
- public TimeSpan UpdateTime { get; private set; }
-
- ///
- /// Gets the last measured render time of the feature
- ///
- public TimeSpan RenderTime { get; private set; }
-
internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction
///
@@ -241,5 +231,47 @@ namespace Artemis.Core
}
#endregion
+
+ #region Timed updates
+
+ ///
+ /// Registers a timed update that whenever the plugin is enabled calls the provided at the
+ /// provided
+ ///
+ ///
+ /// The interval at which the update should occur
+ ///
+ /// The action to call every time the interval has passed. The delta time parameter represents the
+ /// time passed since the last update in seconds
+ ///
+ /// An optional name used in exceptions and profiling
+ /// The resulting plugin update registration which can be used to stop the update
+ public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action action, string? name = null)
+ {
+ if (action == null)
+ throw new ArgumentNullException(nameof(action));
+ return new TimedUpdateRegistration(this, interval, action, name);
+ }
+
+ ///
+ /// Registers a timed update that whenever the plugin is enabled calls the provided at the
+ /// provided
+ ///
+ ///
+ /// The interval at which the update should occur
+ ///
+ /// 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
+ ///
+ /// An optional name used in exceptions and profiling
+ /// The resulting plugin update registration
+ public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func asyncAction, string? name = null)
+ {
+ if (asyncAction == null)
+ throw new ArgumentNullException(nameof(asyncAction));
+ return new TimedUpdateRegistration(this, interval, asyncAction, name);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs
index 1a6af3a88..2773a51c3 100644
--- a/src/Artemis.Core/Plugins/PluginInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginInfo.cs
@@ -124,6 +124,8 @@ namespace Artemis.Core
///
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
+ internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}";
+
///
public override string ToString()
{
diff --git a/src/Artemis.Core/Plugins/Profiling/Profiler.cs b/src/Artemis.Core/Plugins/Profiling/Profiler.cs
index a544bc2f0..c424c19f1 100644
--- a/src/Artemis.Core/Plugins/Profiling/Profiler.cs
+++ b/src/Artemis.Core/Plugins/Profiling/Profiler.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
namespace Artemis.Core
{
@@ -35,13 +37,16 @@ namespace Artemis.Core
/// A unique identifier for this measurement
public void StartMeasurement(string identifier)
{
- if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
+ lock (Measurements)
{
- measurement = new ProfilingMeasurement(identifier);
- Measurements.Add(identifier, measurement);
- }
+ if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
+ {
+ measurement = new ProfilingMeasurement(identifier);
+ Measurements.Add(identifier, measurement);
+ }
- measurement.Start();
+ measurement.Start();
+ }
}
///
@@ -51,13 +56,17 @@ namespace Artemis.Core
/// The number of ticks that passed since the call with the same identifier
public long StopMeasurement(string identifier)
{
- if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
+ long lockRequestedAt = Stopwatch.GetTimestamp();
+ lock (Measurements)
{
- measurement = new ProfilingMeasurement(identifier);
- Measurements.Add(identifier, measurement);
- }
+ if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
+ {
+ measurement = new ProfilingMeasurement(identifier);
+ Measurements.Add(identifier, measurement);
+ }
- return measurement.Stop();
+ return measurement.Stop(Stopwatch.GetTimestamp() - lockRequestedAt);
+ }
}
///
@@ -66,7 +75,10 @@ namespace Artemis.Core
///
public void ClearMeasurements(string identifier)
{
- Measurements.Remove(identifier);
+ lock (Measurements)
+ {
+ Measurements.Remove(identifier);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs b/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
index 4c8aeaae9..eed3a391d 100644
--- a/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
+++ b/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
namespace Artemis.Core
@@ -11,7 +13,8 @@ namespace Artemis.Core
private bool _filledArray;
private int _index;
private long _last;
- private DateTime? _start;
+ private bool _open;
+ private long _start;
internal ProfilingMeasurement(string identifier)
{
@@ -33,22 +36,24 @@ namespace Artemis.Core
///
public void Start()
{
- _start = DateTime.UtcNow;
+ _start = Stopwatch.GetTimestamp();
+ _open = true;
}
///
/// Stops measuring time and stores the time passed in the list
///
+ /// An optional correction in ticks to subtract from the measurement
/// The time passed since the last call
- public long Stop()
+ public long Stop(long correction = 0)
{
- if (_start == null)
+ if (!_open)
return 0;
-
- long difference = (DateTime.UtcNow - _start.Value).Ticks;
+
+ long difference = Stopwatch.GetTimestamp() - _start - correction;
+ _open = false;
Measurements[_index] = difference;
- _start = null;
-
+
_index++;
if (_index >= 1000)
{
@@ -106,5 +111,31 @@ namespace Artemis.Core
? new TimeSpan(Measurements.Max())
: new TimeSpan(Measurements.Take(_index).Max());
}
+
+ ///
+ /// Gets the nth percentile of the last 1000 measurements
+ ///
+ 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];
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs b/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs
index f93225481..65eede731 100644
--- a/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs
+++ b/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using System.Timers;
using Artemis.Core.Modules;
using Artemis.Core.Services;
+using Humanizer;
using Ninject;
using Serilog;
@@ -13,19 +14,20 @@ namespace Artemis.Core
///
public class TimedUpdateRegistration : IDisposable
{
- private DateTime _lastEvent;
- private Timer? _timer;
- private bool _disposed;
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 action)
+ internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action action, string? name)
{
_logger = CoreService.Kernel.Get();
Feature = feature;
Interval = interval;
Action = action;
+ Name = name ?? $"TimedUpdate-{Guid.NewGuid().ToString().Substring(0, 8)}";
Feature.Enabled += FeatureOnEnabled;
Feature.Disabled += FeatureOnDisabled;
@@ -33,13 +35,14 @@ namespace Artemis.Core
Start();
}
- internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func asyncAction)
+ internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func asyncAction, string? name)
{
_logger = CoreService.Kernel.Get();
-
+
Feature = feature;
Interval = interval;
AsyncAction = asyncAction;
+ Name = name ?? $"TimedUpdate-{Guid.NewGuid().ToString().Substring(0, 8)}";
Feature.Enabled += FeatureOnEnabled;
Feature.Disabled += FeatureOnDisabled;
@@ -69,7 +72,12 @@ namespace Artemis.Core
public Func? AsyncAction { get; }
///
- /// Starts calling the or at the configured
+ /// Gets the name of this timed update
+ ///
+ public string Name { get; }
+
+ ///
+ /// Starts calling the or at the configured
/// Note: Called automatically when the plugin enables
///
public void Start()
@@ -93,7 +101,7 @@ namespace Artemis.Core
}
///
- /// Stops calling the or at the configured
+ /// Stops calling the or at the configured
/// Note: Called automatically when the plugin disables
///
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
-
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
@@ -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();
+ }
+
///
public void Dispose()
{
@@ -183,6 +197,12 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
- #endregion
+ ///
+ public sealed override string ToString()
+ {
+ if (Interval.TotalSeconds >= 1)
+ return $"{Name} ({Interval.TotalSeconds} sec)";
+ return $"{Name} ({Interval.TotalMilliseconds} ms)";
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 0d312a262..5655b8112 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -39,9 +39,9 @@ namespace Artemis.Core.Services
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"));
// Remove the old directory if it exists
@@ -81,12 +81,18 @@ namespace Artemis.Core.Services
using StreamReader reader = new(metaDataFileEntry.Open());
PluginInfo builtInPluginInfo = CoreJson.DeserializeObject(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
- DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == Path.GetFileNameWithoutExtension(zipFile.Name));
+ DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == preferred);
if (match == null)
{
- CopyBuiltInPlugin(zipFile, archive);
+ CopyBuiltInPlugin(archive, preferred);
}
else
{
@@ -94,7 +100,7 @@ namespace Artemis.Core.Services
if (!File.Exists(metadataFile))
{
_logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo);
- CopyBuiltInPlugin(zipFile, archive);
+ CopyBuiltInPlugin(archive, preferred);
}
else
{
@@ -114,7 +120,7 @@ namespace Artemis.Core.Services
if (builtInPluginInfo.Version > pluginInfo.Version)
{
_logger.Debug("Copying updated built-in plugin from {pluginInfo} to {builtInPluginInfo}", pluginInfo, builtInPluginInfo);
- CopyBuiltInPlugin(zipFile, archive);
+ CopyBuiltInPlugin(archive, preferred);
}
}
catch (Exception e)
@@ -342,8 +348,8 @@ namespace Artemis.Core.Services
foreach (Type featureType in featureTypes)
{
// Load the enabled state and if not found, default to true
- PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
- new PluginFeatureEntity { IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureType.FullName! };
+ PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.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))));
}
@@ -502,6 +508,7 @@ namespace Artemis.Core.Services
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
if (existing != null)
+ {
try
{
RemovePlugin(existing, false);
@@ -510,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);
}
-
- 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
- DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory));
+ DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, targetDirectory));
Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs
index 7bec70c87..4b47d5e0a 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs
@@ -9,6 +9,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
private string _last;
private string _max;
private string _min;
+ private string _percentile;
public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement)
{
@@ -41,12 +42,19 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
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";
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml
index f8e4cad6a..6fa8f1dba 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml
@@ -27,6 +27,7 @@
+
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml
index cdc24de90..a63321f76 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml
@@ -19,7 +19,7 @@
If you are having performance issues, below you can find out which plugin might be the culprit.
-
+
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml.cs
new file mode 100644
index 000000000..6492ab859
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml.cs
@@ -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
+{
+ ///
+ /// Interaction logic for PerformanceDebugView.xaml
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/Artemis.UI/Screens/TrayView.xaml b/src/Artemis.UI/Screens/TrayView.xaml
index de13735d3..746897449 100644
--- a/src/Artemis.UI/Screens/TrayView.xaml
+++ b/src/Artemis.UI/Screens/TrayView.xaml
@@ -10,8 +10,7 @@
+ DoubleClickCommand="{s:Action TrayBringToForeground}">
diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs
index 42df43ce9..b8d96909d 100644
--- a/src/Artemis.UI/Screens/TrayViewModel.cs
+++ b/src/Artemis.UI/Screens/TrayViewModel.cs
@@ -26,10 +26,11 @@ namespace Artemis.UI.Screens
private readonly IKernel _kernel;
private readonly ThemeWatcher _themeWatcher;
private readonly IWindowManager _windowManager;
+ private ImageSource _icon;
+ private bool _openingMainWindow;
private RootViewModel _rootViewModel;
private SplashViewModel _splashViewModel;
private TaskbarIcon _taskBarIcon;
- private ImageSource _icon;
public TrayViewModel(IKernel kernel,
IWindowManager windowManager,
@@ -55,7 +56,7 @@ namespace Artemis.UI.Screens
_themeWatcher.AppsThemeChanged += _themeWatcher_AppsThemeChanged;
ApplyColorSchemeSetting();
- ApplyTryIconTheme(_themeWatcher.GetSystemTheme());
+ ApplyTrayIconTheme(_themeWatcher.GetSystemTheme());
windowService.ConfigureMainWindowProvider(this);
bool autoRunning = Bootstrapper.StartupArguments.Contains("--autorun");
@@ -63,7 +64,9 @@ namespace Artemis.UI.Screens
bool showOnAutoRun = settingsService.GetSetting("UI.ShowOnStartup", true).Value;
if (autoRunning && !showOnAutoRun || minimized)
+ {
coreService.Initialized += (_, _) => updateService.AutoUpdate();
+ }
else
{
ShowSplashScreen();
@@ -71,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.Closed += RootViewModelOnClosed;
- _windowManager.ShowWindow(_rootViewModel);
- });
-
- OnMainWindowOpened();
- }
-
public ImageSource Icon
{
get => _icon;
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.Closed += RootViewModelOnClosed;
+ _windowManager.ShowWindow(_rootViewModel);
+
+ IsMainWindowOpen = true;
+ _openingMainWindow = false;
+ });
+
+ OnMainWindowOpened();
+ }
+ finally
+ {
+ _openingMainWindow = false;
+ }
+ }
+
public void TrayActivateSidebarItem(string sidebarItem)
{
TrayBringToForeground();
@@ -122,14 +144,6 @@ namespace Artemis.UI.Screens
_taskBarIcon = (TaskbarIcon) ((ContentControl) view).Content;
}
- public void OnTrayBalloonTipClicked(object sender, EventArgs e)
- {
- if (!IsMainWindowOpen)
- TrayBringToForeground();
- else
- FocusMainWindow();
- }
-
private void FocusMainWindow()
{
// Wrestle the main window to the front
@@ -158,10 +172,15 @@ namespace Artemis.UI.Screens
private void RootViewModelOnClosed(object sender, CloseEventArgs e)
{
- if (_rootViewModel != null)
+ lock (this)
{
- _rootViewModel.Closed -= RootViewModelOnClosed;
- _rootViewModel = null;
+ if (_rootViewModel != null)
+ {
+ _rootViewModel.Closed -= RootViewModelOnClosed;
+ _rootViewModel = null;
+ }
+
+ IsMainWindowOpen = false;
}
OnMainWindowClosed();
@@ -187,7 +206,7 @@ namespace Artemis.UI.Screens
ChangeMaterialColors(ApplicationColorScheme.Light);
}
- private void ApplyTryIconTheme(ThemeWatcher.WindowsTheme theme)
+ private void ApplyTrayIconTheme(ThemeWatcher.WindowsTheme theme)
{
Execute.PostToUIThread(() =>
{
@@ -215,7 +234,7 @@ namespace Artemis.UI.Screens
private void _themeWatcher_SystemThemeChanged(object sender, WindowsThemeEventArgs e)
{
- ApplyTryIconTheme(e.Theme);
+ ApplyTrayIconTheme(e.Theme);
}
private void ColorSchemeOnSettingChanged(object sender, EventArgs e)
@@ -231,10 +250,7 @@ namespace Artemis.UI.Screens
public bool OpenMainWindow()
{
- if (IsMainWindowOpen)
- Execute.OnUIThread(FocusMainWindow);
- else
- TrayBringToForeground();
+ TrayBringToForeground();
return _rootViewModel.ScreenState == ScreenState.Active;
}
@@ -250,13 +266,11 @@ namespace Artemis.UI.Screens
protected virtual void OnMainWindowOpened()
{
- IsMainWindowOpen = true;
MainWindowOpened?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnMainWindowClosed()
{
- IsMainWindowOpen = false;
MainWindowClosed?.Invoke(this, EventArgs.Empty);
}