Added a layer of abstraction between RGB.NET and Artemis

This means Artemis can add extra functionality
It also avoid having to reference RGB.NET for basic rendering
Implemented rendering scaling
This commit is contained in:
SpoinkyNL committed 2019-10-30 23:20:06 +01:00
1 parent 498b6a6111
commit 8777e8975f
41 files changed
+725 -494

No files matched your search

+7 -2
View File
@@ -148,13 +148,16 @@
<Compile Include="Exceptions\ArtemisCoreException.cs" />
<Compile Include="Extensions\DirectoryInfoExtensions.cs" />
<Compile Include="Extensions\RgbColorExtensions.cs" />
<Compile Include="Extensions\RgbDeviceExtensions.cs" />
<Compile Include="Extensions\RgbRectangleExtensions.cs" />
<Compile Include="Extensions\TypeExtensions.cs" />
<Compile Include="Models\DataModelDescription.cs" />
<Compile Include="Models\Surface\SurfaceConfiguration.cs" />
<Compile Include="Models\Surface\SurfaceDeviceConfiguration.cs" />
<Compile Include="Models\Surface\Led.cs" />
<Compile Include="Models\Surface\Surface.cs" />
<Compile Include="Models\Surface\Device.cs" />
<Compile Include="Ninject\LoggerProvider.cs" />
<Compile Include="Ninject\PluginSettingsProvider.cs" />
<Compile Include="Ninject\SettingsServiceProvider.cs" />
<Compile Include="Plugins\Abstract\ModuleDataModel.cs" />
<Compile Include="Plugins\Abstract\ModuleViewModel.cs" />
<Compile Include="Plugins\Abstract\ProfileModule.cs" />
@@ -176,6 +179,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RGB.NET\DirectBitmap.cs" />
<Compile Include="RGB.NET\GraphicsDecorator.cs" />
<Compile Include="Services\Interfaces\IProtectedArtemisService.cs" />
<Compile Include="Services\Interfaces\IMainDataModelService.cs" />
<Compile Include="Services\CoreService.cs" />
<Compile Include="Services\MainDataModelService.cs" />
@@ -186,6 +190,7 @@
<Compile Include="Services\Interfaces\IPluginService.cs" />
<Compile Include="Events\PluginEventArgs.cs" />
<Compile Include="Services\PluginService.cs" />
<Compile Include="Services\SettingsService.cs" />
<Compile Include="Services\StorageService.cs" />
<Compile Include="Services\Storage\ISurfaceService.cs" />
<Compile Include="Services\Storage\SurfaceService.cs" />
@@ -5,11 +5,11 @@ namespace Artemis.Core.Events
{
public class SurfaceConfigurationEventArgs : EventArgs
{
public SurfaceConfigurationEventArgs(SurfaceConfiguration surfaceConfiguration)
public SurfaceConfigurationEventArgs(Surface surface)
{
SurfaceConfiguration = surfaceConfiguration;
Surface = surface;
}
public SurfaceConfiguration SurfaceConfiguration { get; }
public Surface Surface { get; }
}
}
@@ -0,0 +1,20 @@
using RGB.NET.Core;
namespace Artemis.Core.Extensions
{
public static class RgbDeviceExtensions
{
public static int GetDeviceHashCode(this IRGBDevice rgbDevice)
{
unchecked
{
var hashCode = rgbDevice.DeviceInfo.DeviceName?.GetHashCode() ?? 0;
hashCode = (hashCode * 397) ^ (rgbDevice.DeviceInfo.Manufacturer?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (rgbDevice.DeviceInfo.Model?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (int) rgbDevice.DeviceInfo.DeviceType;
hashCode = (hashCode * 397) ^ (int) rgbDevice.DeviceInfo.Lighting;
return hashCode;
}
}
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ namespace Artemis.Core.Models.Profile
profileElement.Update(deltaTime);
}
public void Render(double deltaTime, RGBSurface surface, Graphics graphics)
public void Render(double deltaTime, Surface.Surface surface, Graphics graphics)
{
// Folders don't render but their children do
foreach (var profileElement in Children)
@@ -30,6 +30,6 @@ namespace Artemis.Core.Models.Profile.Interfaces
/// <summary>
/// Renders the element
/// </summary>
void Render(double deltaTime, RGBSurface surface, Graphics graphics);
void Render(double deltaTime, Surface.Surface surface, Graphics graphics);
}
}
+1 -2
View File
@@ -6,7 +6,6 @@ using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Interfaces;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Entities;
using RGB.NET.Core;
namespace Artemis.Core.Models.Profile
{
@@ -36,7 +35,7 @@ namespace Artemis.Core.Models.Profile
}
}
public void Render(double deltaTime, RGBSurface surface, Graphics graphics)
public void Render(double deltaTime, Surface.Surface surface, Graphics graphics)
{
if (LayerType == null)
return;
+1 -1
View File
@@ -35,7 +35,7 @@ namespace Artemis.Core.Models.Profile
}
}
public void Render(double deltaTime, RGBSurface surface, Graphics graphics)
public void Render(double deltaTime, Surface.Surface surface, Graphics graphics)
{
lock (this)
{
+105
View File
@@ -0,0 +1,105 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.Extensions;
using Artemis.Storage.Entities;
using RGB.NET.Core;
using Rectangle = System.Drawing.Rectangle;
namespace Artemis.Core.Models.Surface
{
public class Device
{
internal Device(IRGBDevice rgbDevice, Surface surface)
{
RgbDevice = rgbDevice;
Surface = surface;
Configuration = new DeviceEntity();
Leds = rgbDevice.Select(l => new DeviceLed(l, this)).ToList().AsReadOnly();
Rotation = 0;
ZIndex = 1;
ApplyToConfiguration();
CalculateRenderRectangle();
}
internal Device(IRGBDevice rgbDevice, Surface surface, DeviceEntity configuration)
{
RgbDevice = rgbDevice;
Surface = surface;
Configuration = configuration;
Leds = rgbDevice.Select(l => new DeviceLed(l, this)).ToList().AsReadOnly();
Rotation = configuration.Rotation;
ZIndex = configuration.ZIndex;
}
public Rectangle RenderRectangle { get; private set; }
public IRGBDevice RgbDevice { get; private set; }
public Surface Surface { get; private set; }
public DeviceEntity Configuration { get; private set; }
public ReadOnlyCollection<DeviceLed> Leds { get; set; }
public double X
{
get => Configuration.X;
set => Configuration.X = value;
}
public double Y
{
get => Configuration.Y;
set => Configuration.Y = value;
}
public double Rotation
{
get => Configuration.Rotation;
set => Configuration.Rotation = value;
}
public int ZIndex
{
get => Configuration.ZIndex;
set => Configuration.ZIndex = value;
}
internal void ApplyToConfiguration()
{
Configuration.SurfaceId = Surface.Guid;
Configuration.DeviceHashCode = RgbDevice.GetDeviceHashCode();
// Ensure the position configuration is in the surface configuration's' collection of positions
if (Surface.SurfaceEntity.DeviceEntities.All(p => p.Guid != Configuration.Guid))
Surface.SurfaceEntity.DeviceEntities.Add(Configuration);
}
internal void ApplyToRgbDevice()
{
RgbDevice.Location = new Point(Configuration.X, Configuration.Y);
CalculateRenderRectangle();
}
internal void CalculateRenderRectangle()
{
RenderRectangle = new Rectangle(
(int) Math.Round(RgbDevice.Location.X * Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbDevice.Location.Y * Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbDevice.Size.Width * Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbDevice.Size.Height * Surface.Scale, MidpointRounding.AwayFromZero)
);
foreach (var led in Leds)
led.CalculateRenderRectangle();
}
internal void Destroy()
{
Configuration = null;
RgbDevice = null;
Surface = null;
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using RGB.NET.Core;
using Rectangle = System.Drawing.Rectangle;
namespace Artemis.Core.Models.Surface
{
public class DeviceLed
{
public DeviceLed(Led led, Device device)
{
RgbLed = led;
Device = device;
CalculateRenderRectangle();
}
public Led RgbLed { get; }
public Device Device { get; }
public Rectangle RenderRectangle { get; private set; }
public Rectangle AbsoluteRenderRectangle { get; private set; }
public void CalculateRenderRectangle()
{
RenderRectangle = new Rectangle(
(int) Math.Round(RgbLed.LedRectangle.X * Device.Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbLed.LedRectangle.Y * Device.Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbLed.LedRectangle.Width * Device.Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbLed.LedRectangle.Height * Device.Surface.Scale, MidpointRounding.AwayFromZero)
);
AbsoluteRenderRectangle = new Rectangle(
(int) Math.Round(RgbLed.AbsoluteLedRectangle.X * Device.Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbLed.AbsoluteLedRectangle.Y * Device.Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbLed.AbsoluteLedRectangle.Width * Device.Surface.Scale, MidpointRounding.AwayFromZero),
(int) Math.Round(RgbLed.AbsoluteLedRectangle.Height * Device.Surface.Scale, MidpointRounding.AwayFromZero)
);
}
}
}
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Extensions;
using Artemis.Storage.Entities;
using RGB.NET.Core;
namespace Artemis.Core.Models.Surface
{
public class Surface
{
internal Surface(RGBSurface rgbSurface, string name, double scale)
{
SurfaceEntity = new SurfaceEntity {DeviceEntities = new List<DeviceEntity>()};
Guid = System.Guid.NewGuid().ToString();
Name = name;
Scale = scale;
RgbSurface = rgbSurface;
IsActive = false;
Devices = new List<Device>();
ApplyToEntity();
}
internal Surface(RGBSurface rgbSurface, SurfaceEntity surfaceEntity, double scale)
{
RgbSurface = rgbSurface;
SurfaceEntity = surfaceEntity;
Scale = scale;
Guid = surfaceEntity.Guid;
Name = surfaceEntity.Name;
IsActive = surfaceEntity.IsActive;
Devices = new List<Device>();
if (surfaceEntity.DeviceEntities == null)
return;
foreach (var position in surfaceEntity.DeviceEntities)
{
var device = rgbSurface.Devices.FirstOrDefault(d => d.GetDeviceHashCode() == position.DeviceHashCode);
if (device != null)
Devices.Add(new Device(device, this, position));
}
}
public RGBSurface RgbSurface { get; }
public double Scale { get; private set; }
public string Name { get; set; }
public bool IsActive { get; internal set; }
public List<Device> Devices { get; internal set; }
internal SurfaceEntity SurfaceEntity { get; set; }
internal string Guid { get; set; }
internal void ApplyToEntity()
{
SurfaceEntity.Guid = Guid;
SurfaceEntity.Name = Name;
SurfaceEntity.IsActive = IsActive;
}
internal void Destroy()
{
SurfaceEntity = null;
foreach (var deviceConfiguration in Devices)
deviceConfiguration.Destroy();
Devices.Clear();
}
public void UpdateScale(double value)
{
Scale = value;
foreach (var device in Devices)
device.CalculateRenderRectangle();
OnScaleChanged();
}
#region Events
public event EventHandler<EventArgs> ScaleChanged;
protected virtual void OnScaleChanged()
{
ScaleChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}
@@ -1,58 +0,0 @@
using System.Collections.Generic;
using Artemis.Storage.Entities;
namespace Artemis.Core.Models.Surface
{
public class SurfaceConfiguration
{
internal SurfaceConfiguration(string name)
{
SurfaceEntity = new SurfaceEntity {SurfacePositions = new List<SurfacePositionEntity>()};
Guid = System.Guid.NewGuid().ToString();
Name = name;
IsActive = false;
DeviceConfigurations = new List<SurfaceDeviceConfiguration>();
ApplyToEntity();
}
internal SurfaceConfiguration(SurfaceEntity surfaceEntity)
{
SurfaceEntity = surfaceEntity;
Guid = surfaceEntity.Guid;
Name = surfaceEntity.Name;
IsActive = surfaceEntity.IsActive;
DeviceConfigurations = new List<SurfaceDeviceConfiguration>();
if (surfaceEntity.SurfacePositions == null)
return;
foreach (var position in surfaceEntity.SurfacePositions)
DeviceConfigurations.Add(new SurfaceDeviceConfiguration(position, this));
}
internal SurfaceEntity SurfaceEntity { get; set; }
internal string Guid { get; set; }
public string Name { get; set; }
public bool IsActive { get; internal set; }
public List<SurfaceDeviceConfiguration> DeviceConfigurations { get; internal set; }
internal void ApplyToEntity()
{
SurfaceEntity.Guid = Guid;
SurfaceEntity.Name = Name;
SurfaceEntity.IsActive = IsActive;
}
internal void Destroy()
{
SurfaceEntity = null;
foreach (var deviceConfiguration in DeviceConfigurations)
deviceConfiguration.Destroy();
DeviceConfigurations.Clear();
}
}
}
@@ -1,105 +0,0 @@
using System.Linq;
using Artemis.Storage.Entities;
using RGB.NET.Core;
namespace Artemis.Core.Models.Surface
{
public class SurfaceDeviceConfiguration
{
internal SurfaceDeviceConfiguration(IRGBDevice device, int deviceId, SurfaceConfiguration surface)
{
PositionEntity = new SurfacePositionEntity();
Guid = System.Guid.NewGuid().ToString();
Device = device;
DeviceId = deviceId;
DeviceName = device.DeviceInfo.DeviceName;
DeviceModel = device.DeviceInfo.Model;
DeviceManufacturer = device.DeviceInfo.Manufacturer;
X = device.Location.X;
Y = device.Location.Y;
Rotation = 0;
ZIndex = 1;
Surface = surface;
ApplyToEntity();
}
internal SurfaceDeviceConfiguration(SurfacePositionEntity positionEntity, SurfaceConfiguration surfaceConfiguration)
{
PositionEntity = positionEntity;
Guid = positionEntity.Guid;
DeviceId = positionEntity.DeviceId;
DeviceName = positionEntity.DeviceName;
DeviceModel = positionEntity.DeviceModel;
DeviceManufacturer = positionEntity.DeviceManufacturer;
X = positionEntity.X;
Y = positionEntity.Y;
Rotation = positionEntity.Rotation;
ZIndex = positionEntity.ZIndex;
Surface = surfaceConfiguration;
}
internal SurfacePositionEntity PositionEntity { get; set; }
internal string Guid { get; }
public IRGBDevice Device { get; internal set; }
public int DeviceId { get; }
public string DeviceName { get; }
public string DeviceModel { get; }
public string DeviceManufacturer { get; }
public double X { get; set; }
public double Y { get; set; }
public double Rotation { get; set; }
public int ZIndex { get; set; }
public SurfaceConfiguration Surface { get; private set; }
/// <summary>
/// Applies the configuration to the device
/// </summary>
internal void ApplyToDevice()
{
if (Device != null)
{
Device.Location = new Point(X, Y);
}
}
/// <summary>
/// Must be called when saving to the database
/// </summary>
internal void ApplyToEntity()
{
PositionEntity.Guid = Guid;
PositionEntity.SurfaceId = Surface.Guid;
PositionEntity.DeviceId = DeviceId;
PositionEntity.DeviceName = DeviceName;
PositionEntity.DeviceModel = DeviceModel;
PositionEntity.DeviceManufacturer = DeviceManufacturer;
PositionEntity.X = X;
PositionEntity.Y = Y;
PositionEntity.Rotation = Rotation;
PositionEntity.ZIndex = ZIndex;
// Ensure the position entity is in the surface entity's' collection of positions
if (Surface.SurfaceEntity.SurfacePositions.All(p => p.Guid != Guid))
Surface.SurfaceEntity.SurfacePositions.Add(PositionEntity);
}
public void Destroy()
{
PositionEntity = null;
Device = null;
Surface = null;
}
}
}
+12 -1
View File
@@ -1,5 +1,6 @@
using Artemis.Core.Exceptions;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Repositories.Interfaces;
using Ninject.Extensions.Conventions;
@@ -25,6 +26,16 @@ namespace Artemis.Core.Ninject
.Configure(c => c.InSingletonScope());
});
// Bind all protected services as singletons TODO: Protect 'em
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<IProtectedArtemisService>()
.BindAllInterfaces()
.Configure(c => c.InSingletonScope());
});
// Bind all repositories as singletons
Kernel.Bind(x =>
{
@@ -34,7 +45,7 @@ namespace Artemis.Core.Ninject
.BindAllInterfaces()
.Configure(c => c.InSingletonScope());
});
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
}
@@ -0,0 +1,30 @@
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Exceptions;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services;
using Ninject;
using Ninject.Activation;
namespace Artemis.Core.Ninject
{
public class SettingsServiceProvider : Provider<ISettingsService>
{
private readonly SettingsService _instance;
public SettingsServiceProvider(IKernel kernel)
{
// This is not lazy, but the core is always going to be using this anyway
_instance = kernel.Get<SettingsService>();
}
protected override ISettingsService CreateInstance(IContext context)
{
var parentRequest = context.Request.ParentRequest;
if (parentRequest == null || typeof(Plugin).IsAssignableFrom(parentRequest.Service))
throw new ArtemisPluginException($"SettingsService can not be injected into a plugin. Inject {nameof(PluginSettings)} instead.");
return _instance;
}
}
}
@@ -1,5 +1,6 @@
using System.Drawing;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Models;
using RGB.NET.Core;
@@ -24,6 +25,6 @@ namespace Artemis.Core.Plugins.Abstract
/// <summary>
/// Renders the layer type
/// </summary>
public abstract void Render(Layer device, RGBSurface surface, Graphics graphics);
public abstract void Render(Layer device, Surface surface, Graphics graphics);
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
using System.Drawing;
using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Models;
using RGB.NET.Core;
using Stylet;
@@ -38,7 +39,7 @@ namespace Artemis.Core.Plugins.Abstract
/// <param name="deltaTime">Time since the last render</param>
/// <param name="surface">The RGB Surface to render to</param>
/// <param name="graphics"></param>
public abstract void Render(double deltaTime, RGBSurface surface, Graphics graphics);
public abstract void Render(double deltaTime, Surface surface, Graphics graphics);
/// <summary>
/// Called when the module's main view is being shown
@@ -1,6 +1,7 @@
using System;
using System.Drawing;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Models;
using RGB.NET.Core;
@@ -25,7 +26,7 @@ namespace Artemis.Core.Plugins.Abstract
}
/// <inheritdoc />
public override void Render(double deltaTime, RGBSurface surface, Graphics graphics)
public override void Render(double deltaTime, Surface surface, Graphics graphics)
{
lock (this)
{
@@ -1,11 +1,14 @@
using System.Threading.Tasks;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Artemis.Storage.Entities;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Stylet;
namespace Artemis.Core.Plugins.Models
{
public class PluginSetting<T>
public class PluginSetting<T> : PropertyChangedBase
{
// ReSharper disable once NotAccessedField.Local
private readonly PluginInfo _pluginInfo;
@@ -20,6 +23,15 @@ namespace Artemis.Core.Plugins.Models
Name = pluginSettingEntity.Name;
Value = JsonConvert.DeserializeObject<T>(pluginSettingEntity.Value);
// PropertyChanged is for bindings, but we can use it here to create a easy to use SettingsChanged event
PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Value))
OnSettingChanged();
}
/// <summary>
@@ -69,5 +81,12 @@ namespace Artemis.Core.Plugins.Models
_pluginSettingEntity.Value = JsonConvert.SerializeObject(Value);
await _pluginSettingRepository.SaveAsync();
}
public event EventHandler<EventArgs> SettingChanged;
protected virtual void OnSettingChanged()
{
SettingChanged?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -6,6 +6,10 @@ using Newtonsoft.Json;
namespace Artemis.Core.Plugins.Models
{
/// <summary>
/// <para>This contains all the settings for your plugin. To access a setting use <see cref="GetSetting{T}" />.</para>
/// <para>To use this class, inject it into the constructor of your plugin.</para>
/// </summary>
public class PluginSettings
{
private readonly PluginInfo _pluginInfo;
@@ -19,17 +23,27 @@ namespace Artemis.Core.Plugins.Models
_settingEntities = pluginSettingRepository.GetByPluginGuid(_pluginInfo.Guid).ToDictionary(se => se.Name);
}
/// <summary>
/// Gets the setting with the provided name. If the setting does not exist yet, it is created.
/// </summary>
/// <typeparam name="T">The type of the setting, can be any serializable type</typeparam>
/// <param name="name">The name of the setting</param>
/// <param name="defaultValue">The default value to use if the setting does not exist yet</param>
/// <returns></returns>
public PluginSetting<T> GetSetting<T>(string name, T defaultValue = default(T))
{
if (_settingEntities.ContainsKey(name))
lock (_settingEntities)
{
if (_settingEntities.ContainsKey(name))
return new PluginSetting<T>(_pluginInfo, _pluginSettingRepository, _settingEntities[name]);
var settingEntity = new PluginSettingEntity {Name = name, PluginGuid = _pluginInfo.Guid, Value = JsonConvert.SerializeObject(defaultValue)};
_pluginSettingRepository.Add(settingEntity);
_pluginSettingRepository.Save();
_settingEntities.Add(name, settingEntity);
return new PluginSetting<T>(_pluginInfo, _pluginSettingRepository, _settingEntities[name]);
var settingEntity = new PluginSettingEntity {Name = name, PluginGuid = _pluginInfo.Guid, Value = JsonConvert.SerializeObject(defaultValue)};
_pluginSettingRepository.Add(settingEntity);
_pluginSettingRepository.Save();
_settingEntities.Add(name, settingEntity);
return new PluginSetting<T>(_pluginInfo, _pluginSettingRepository, _settingEntities[name]);
}
}
}
}
+4 -4
View File
@@ -52,11 +52,11 @@ namespace Artemis.Core.Services
await Task.Run(() => _pluginService.CopyBuiltInPlugins());
await Task.Run(() => _pluginService.LoadPlugins());
var surfaceConfig = _surfaceService.ActiveSurfaceConfiguration;
var surfaceConfig = _surfaceService.ActiveSurface;
if (surfaceConfig != null)
_logger.Information("Initialized with active surface configuration {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.Guid);
_logger.Information("Initialized with active surface entity {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.Guid);
else
_logger.Information("Initialized without an active surface configuration");
_logger.Information("Initialized without an active surface entity");
OnInitialized();
}
@@ -84,7 +84,7 @@ namespace Artemis.Core.Services
g.Clear(Color.Black);
foreach (var module in modules)
module.Render(args.DeltaTime, _rgbService.Surface, g);
module.Render(args.DeltaTime, _surfaceService.ActiveSurface, g);
}
OnFrameRendered(new FrameEventArgs(modules, _rgbService.GraphicsDecorator.GetBitmap(), args.DeltaTime, _rgbService.Surface));
@@ -1,5 +1,8 @@
namespace Artemis.Core.Services.Interfaces
{
/// <summary>
/// A singleton service
/// </summary>
public interface IArtemisService
{
}
@@ -0,0 +1,9 @@
namespace Artemis.Core.Services.Interfaces
{
/// <summary>
/// A singleton service that cannot be used by plugins
/// </summary>
public interface IProtectedArtemisService
{
}
}
+1 -1
View File
@@ -102,7 +102,7 @@ namespace Artemis.Core.Services
// Create a child kernel and app domain that will only contain the plugins
_childKernel = new ChildKernel(_kernel);
// Load the plugin assemblies into the plugin context
var pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins"));
foreach (var subDirectory in pluginDirectory.EnumerateDirectories())
+22 -25
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Events;
using Artemis.Core.Plugins.Models;
using Artemis.Core.RGB.NET;
using Artemis.Core.Services.Interfaces;
using Artemis.Core.Services.Storage;
@@ -20,15 +21,18 @@ namespace Artemis.Core.Services
private readonly ILogger _logger;
private readonly TimerUpdateTrigger _updateTrigger;
private ListLedGroup _background;
private PluginSetting<double> _renderScaleSetting;
internal RgbService(ILogger logger)
internal RgbService(ILogger logger, ISettingsService settingsService)
{
_logger = logger;
_renderScaleSetting = settingsService.GetSetting("RenderScale", 1.0);
Surface = RGBSurface.Instance;
// Let's throw these for now
Surface.Exception += SurfaceOnException;
_renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged;
_loadedDevices = new List<IRGBDevice>();
_updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / 25};
Surface.RegisterUpdateTrigger(_updateTrigger);
@@ -39,16 +43,7 @@ namespace Artemis.Core.Services
public GraphicsDecorator GraphicsDecorator { get; private set; }
public IReadOnlyCollection<IRGBDevice> LoadedDevices
{
get
{
lock (_loadedDevices)
{
return _loadedDevices.AsReadOnly();
}
}
}
public IReadOnlyCollection<IRGBDevice> LoadedDevices => _loadedDevices.AsReadOnly();
public void AddDeviceProvider(IRGBDeviceProvider deviceProvider)
{
@@ -56,23 +51,20 @@ namespace Artemis.Core.Services
if (deviceProvider.Devices == null)
{
_logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
_logger.Warning("RgbDevice provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
return;
}
lock (_loadedDevices)
foreach (var surfaceDevice in deviceProvider.Devices)
{
foreach (var surfaceDevice in deviceProvider.Devices)
if (!_loadedDevices.Contains(surfaceDevice))
{
if (!_loadedDevices.Contains(surfaceDevice))
{
_loadedDevices.Add(surfaceDevice);
OnDeviceLoaded(new DeviceEventArgs(surfaceDevice));
}
else
{
OnDeviceReloaded(new DeviceEventArgs(surfaceDevice));
}
_loadedDevices.Add(surfaceDevice);
OnDeviceLoaded(new DeviceEventArgs(surfaceDevice));
}
else
{
OnDeviceReloaded(new DeviceEventArgs(surfaceDevice));
}
}
}
@@ -85,6 +77,11 @@ namespace Artemis.Core.Services
Surface.Dispose();
}
private void RenderScaleSettingOnSettingChanged(object sender, EventArgs e)
{
UpdateGraphicsDecorator();
}
private void SurfaceOnException(ExceptionEventArgs args)
{
throw args.Exception;
@@ -108,7 +105,7 @@ namespace Artemis.Core.Services
// Apply the application wide brush and decorator
_background = new ListLedGroup(Surface.Leds) {Brush = new SolidColorBrush(new Color(255, 255, 255, 255))};
GraphicsDecorator = new GraphicsDecorator(_background, 0.25);
GraphicsDecorator = new GraphicsDecorator(_background, _renderScaleSetting.Value);
_background.Brush.RemoveAllDecorators();
_background.Brush.AddDecorator(GraphicsDecorator);
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Entities;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
namespace Artemis.Core.Services
{
/// <inheritdoc />
public class SettingsService : ISettingsService
{
private readonly PluginInfo _buildInPluginInfo;
private readonly IPluginSettingRepository _pluginSettingRepository;
private readonly Dictionary<string, PluginSettingEntity> _settingEntities;
internal SettingsService(IPluginSettingRepository pluginSettingRepository)
{
_pluginSettingRepository = pluginSettingRepository;
_buildInPluginInfo = new PluginInfo {Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core"};
_settingEntities = pluginSettingRepository.GetByPluginGuid(_buildInPluginInfo.Guid).ToDictionary(se => se.Name);
}
public PluginSetting<T> GetSetting<T>(string name, T defaultValue = default(T))
{
lock (_settingEntities)
{
if (_settingEntities.ContainsKey(name))
return new PluginSetting<T>(_buildInPluginInfo, _pluginSettingRepository, _settingEntities[name]);
var settingEntity = new PluginSettingEntity {Name = name, PluginGuid = _buildInPluginInfo.Guid, Value = JsonConvert.SerializeObject(defaultValue)};
_pluginSettingRepository.Add(settingEntity);
_pluginSettingRepository.Save();
_settingEntities.Add(name, settingEntity);
return new PluginSetting<T>(_buildInPluginInfo, _pluginSettingRepository, _settingEntities[name]);
}
}
}
/// <summary>
/// <para>A wrapper around plugin settings for internal use.</para>
/// <para>Do not inject into a plugin, for plugins inject <see cref="PluginSettings" /> instead.</para>
/// </summary>
public interface ISettingsService : IProtectedArtemisService
{
PluginSetting<T> GetSetting<T>(string name, T defaultValue = default(T));
}
}
@@ -9,43 +9,43 @@ namespace Artemis.Core.Services.Storage
public interface ISurfaceService : IArtemisService
{
/// <summary>
/// Gets the currently active surface configuration, to change config use <see cref="SetActiveSurfaceConfiguration" />
/// Gets the currently active surface entity, to change config use <see cref="SetActiveSurfaceConfiguration" />
/// </summary>
SurfaceConfiguration ActiveSurfaceConfiguration { get; }
Surface ActiveSurface { get; }
/// <summary>
/// Gets a read-only list of all surface configurations
/// </summary>
ReadOnlyCollection<SurfaceConfiguration> SurfaceConfigurations { get; }
ReadOnlyCollection<Surface> SurfaceConfigurations { get; }
/// <summary>
/// Creates a new surface configuration with the supplied name
/// Creates a new surface entity with the supplied name
/// </summary>
/// <param name="name">The name for the new surface configuration</param>
/// <param name="name">The name for the new surface entity</param>
/// <returns></returns>
SurfaceConfiguration CreateSurfaceConfiguration(string name);
Surface CreateSurfaceConfiguration(string name);
/// <summary>
/// Sets the provided configuration as active and applies it to the surface
/// Sets the provided entity as active and applies it to the surface
/// </summary>
/// <param name="surfaceConfiguration">The configuration to activate and apply</param>
void SetActiveSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration);
/// <param name="surface">The entity to activate and apply</param>
void SetActiveSurfaceConfiguration(Surface surface);
/// <summary>
/// Saves the provided surface configuration to permanent storage and if config is active, applies it to the surface
/// Saves the provided surface entity to permanent storage and if config is active, applies it to the surface
/// </summary>
/// <param name="surfaceConfiguration">The configuration to save (and apply if active)</param>
/// <param name="surface">The entity to save (and apply if active)</param>
/// <param name="includeDevices">Whether to also save devices. If false, devices changes won't be applied either</param>
void UpdateSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration, bool includeDevices);
void UpdateSurfaceConfiguration(Surface surface, bool includeDevices);
/// <summary>
/// Deletes the supplied surface configuration, surface configuration may not be the active surface configuration
/// Deletes the supplied surface entity, surface entity may not be the active surface entity
/// </summary>
/// <param name="surfaceConfiguration">The surface configuration to delete, may not be the active surface configuration</param>
void DeleteSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration);
/// <param name="surface">The surface entity to delete, may not be the active surface entity</param>
void DeleteSurfaceConfiguration(Surface surface);
/// <summary>
/// Occurs when the active device configuration has been changed
/// Occurs when the active device entity has been changed
/// </summary>
event EventHandler<SurfaceConfigurationEventArgs> ActiveSurfaceConfigurationChanged;
}
@@ -4,7 +4,9 @@ using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.Events;
using Artemis.Core.Exceptions;
using Artemis.Core.Extensions;
using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Repositories.Interfaces;
using RGB.NET.Core;
@@ -16,45 +18,37 @@ namespace Artemis.Core.Services.Storage
{
private readonly ILogger _logger;
private readonly IRgbService _rgbService;
private readonly List<SurfaceConfiguration> _surfaceConfigurations;
private readonly List<Surface> _surfaceConfigurations;
private readonly ISurfaceRepository _surfaceRepository;
private readonly PluginSetting<double> _renderScaleSetting;
public SurfaceService(ILogger logger, ISurfaceRepository surfaceRepository, IRgbService rgbService)
internal SurfaceService(ILogger logger, ISurfaceRepository surfaceRepository, IRgbService rgbService, ISettingsService settingsService)
{
_logger = logger;
_surfaceRepository = surfaceRepository;
_rgbService = rgbService;
_surfaceConfigurations = new List<SurfaceConfiguration>();
_surfaceConfigurations = new List<Surface>();
_renderScaleSetting = settingsService.GetSetting("RenderScale", 1.0);
LoadFromRepository();
_rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded;
_renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged;
}
public SurfaceConfiguration ActiveSurfaceConfiguration { get; private set; }
public ReadOnlyCollection<SurfaceConfiguration> SurfaceConfigurations
{
get
{
lock (_surfaceConfigurations)
{
return _surfaceConfigurations.AsReadOnly();
}
}
}
public SurfaceConfiguration CreateSurfaceConfiguration(string name)
public Surface ActiveSurface { get; private set; }
public ReadOnlyCollection<Surface> SurfaceConfigurations => _surfaceConfigurations.AsReadOnly();
public Surface CreateSurfaceConfiguration(string name)
{
// Create a blank config
var configuration = new SurfaceConfiguration(name);
var configuration = new Surface(_rgbService.Surface, name, _renderScaleSetting.Value);
// Add all current devices
foreach (var rgbDevice in _rgbService.LoadedDevices)
{
var deviceId = GetDeviceId(rgbDevice);
configuration.DeviceConfigurations.Add(new SurfaceDeviceConfiguration(rgbDevice, deviceId, configuration));
}
foreach (var rgbDevice in _rgbService.LoadedDevices)
configuration.Devices.Add(new Device(rgbDevice, configuration));
lock (_surfaceConfigurations)
{
@@ -64,67 +58,67 @@ namespace Artemis.Core.Services.Storage
}
}
public void SetActiveSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration)
public void SetActiveSurfaceConfiguration(Surface surface)
{
if (ActiveSurfaceConfiguration == surfaceConfiguration)
if (ActiveSurface == surface)
return;
// Set the new configuration
ActiveSurfaceConfiguration = surfaceConfiguration;
// Set the new entity
ActiveSurface = surface;
// Ensure only the new configuration is marked as active
// Ensure only the new entity is marked as active
lock (_surfaceConfigurations)
{
// Mark only the new surfaceConfiguration as active
// Mark only the new surface as active
foreach (var configuration in _surfaceConfigurations)
{
configuration.IsActive = configuration == ActiveSurfaceConfiguration;
configuration.IsActive = configuration == ActiveSurface;
configuration.ApplyToEntity();
}
_surfaceRepository.Save();
}
// Apply the active surface configuration to the devices
if (ActiveSurfaceConfiguration != null)
// Apply the active surface entity to the devices
if (ActiveSurface != null)
{
foreach (var deviceConfiguration in ActiveSurfaceConfiguration.DeviceConfigurations)
deviceConfiguration.ApplyToDevice();
foreach (var device in ActiveSurface.Devices)
device.ApplyToRgbDevice();
}
// Update the RGB service's graphics decorator to work with the new surface configuration
// Update the RGB service's graphics decorator to work with the new surface entity
_rgbService.UpdateGraphicsDecorator();
OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(ActiveSurfaceConfiguration));
OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(ActiveSurface));
}
public void UpdateSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration, bool includeDevices)
public void UpdateSurfaceConfiguration(Surface surface, bool includeDevices)
{
surfaceConfiguration.ApplyToEntity();
surface.ApplyToEntity();
if (includeDevices)
{
foreach (var deviceConfiguration in surfaceConfiguration.DeviceConfigurations)
foreach (var deviceConfiguration in surface.Devices)
{
deviceConfiguration.ApplyToEntity();
if (surfaceConfiguration.IsActive)
deviceConfiguration.ApplyToDevice();
deviceConfiguration.ApplyToConfiguration();
if (surface.IsActive)
deviceConfiguration.ApplyToRgbDevice();
}
}
_surfaceRepository.Save();
_rgbService.UpdateGraphicsDecorator();
OnSurfaceConfigurationUpdated(new SurfaceConfigurationEventArgs(surfaceConfiguration));
OnSurfaceConfigurationUpdated(new SurfaceConfigurationEventArgs(surface));
}
public void DeleteSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration)
public void DeleteSurfaceConfiguration(Surface surface)
{
if (surfaceConfiguration == ActiveSurfaceConfiguration)
throw new ArtemisCoreException($"Cannot delete surface configuration '{surfaceConfiguration.Name}' because it is active.");
if (surface == ActiveSurface)
throw new ArtemisCoreException($"Cannot delete surface entity '{surface.Name}' because it is active.");
lock (_surfaceConfigurations)
{
var entity = surfaceConfiguration.SurfaceEntity;
surfaceConfiguration.Destroy();
_surfaceConfigurations.Remove(surfaceConfiguration);
var entity = surface.SurfaceEntity;
surface.Destroy();
_surfaceConfigurations.Remove(surface);
_surfaceRepository.Remove(entity);
_surfaceRepository.Save();
@@ -138,10 +132,17 @@ namespace Artemis.Core.Services.Storage
lock (_surfaceConfigurations)
{
foreach (var surfaceConfiguration in _surfaceConfigurations)
MatchDeviceConfiguration(e.Device, surfaceConfiguration);
AddDeviceIfMissing(e.Device, surfaceConfiguration);
}
UpdateSurfaceConfiguration(ActiveSurfaceConfiguration, true);
UpdateSurfaceConfiguration(ActiveSurface, true);
}
private void RenderScaleSettingOnSettingChanged(object sender, EventArgs e)
{
foreach (var surfaceConfiguration in SurfaceConfigurations)
surfaceConfiguration.UpdateScale(_renderScaleSetting.Value);
}
#endregion
@@ -153,12 +154,9 @@ namespace Artemis.Core.Services.Storage
var configs = _surfaceRepository.GetAll();
foreach (var surfaceEntity in configs)
{
// Create the surface configuration
var surfaceConfiguration = new SurfaceConfiguration(surfaceEntity);
// For each loaded device, match a device configuration
var devices = _rgbService.LoadedDevices;
foreach (var rgbDevice in devices)
MatchDeviceConfiguration(rgbDevice, surfaceConfiguration);
// Create the surface entity
var surfaceConfiguration = new Surface(_rgbService.Surface, surfaceEntity, _renderScaleSetting.Value);
// Finally, add the surface config to the collection
lock (_surfaceConfigurations)
{
@@ -170,39 +168,44 @@ namespace Artemis.Core.Services.Storage
var active = SurfaceConfigurations.FirstOrDefault(c => c.IsActive);
if (active != null)
SetActiveSurfaceConfiguration(active);
else
{
active = SurfaceConfigurations.FirstOrDefault();
if (active != null)
SetActiveSurfaceConfiguration(active);
else
SetActiveSurfaceConfiguration(CreateSurfaceConfiguration("Default"));
}
}
#endregion
#region Utilities
private void MatchDeviceConfiguration(IRGBDevice rgbDevice, SurfaceConfiguration surfaceConfiguration)
private void AddDeviceIfMissing(IRGBDevice rgbDevice, Surface surface)
{
var deviceId = GetDeviceId(rgbDevice);
var deviceConfig = surfaceConfiguration.DeviceConfigurations.FirstOrDefault(d => d.DeviceName == rgbDevice.DeviceInfo.DeviceName &&
d.DeviceModel == rgbDevice.DeviceInfo.Model &&
d.DeviceManufacturer == rgbDevice.DeviceInfo.Manufacturer &&
d.DeviceId == deviceId);
var deviceHashCode = rgbDevice.GetDeviceHashCode();
var device = surface.Devices.FirstOrDefault(d => d.Configuration.DeviceHashCode == deviceHashCode);
if (deviceConfig == null)
if (device != null)
return;
// Find an existing device config and use that
var existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceHashCode == deviceHashCode);
if (existingDeviceConfig != null)
device = new Device(rgbDevice,surface, existingDeviceConfig);
// Fall back on creating a new device
else
{
_logger.Information("No active surface config found for {deviceInfo}, device ID: {deviceId}. Adding a new entry.", rgbDevice.DeviceInfo, deviceId);
deviceConfig = new SurfaceDeviceConfiguration(rgbDevice, deviceId, surfaceConfiguration);
surfaceConfiguration.DeviceConfigurations.Add(deviceConfig);
_logger.Information(
"No device config found for {deviceInfo}, device hash: {deviceHashCode}. Adding a new entry.",
rgbDevice.DeviceInfo,
deviceHashCode
);
device = new Device(rgbDevice, surface);
}
deviceConfig.Device = rgbDevice;
deviceConfig.ApplyToDevice();
}
private int GetDeviceId(IRGBDevice rgbDevice)
{
return _rgbService.LoadedDevices
.Where(d => d.DeviceInfo.DeviceName == rgbDevice.DeviceInfo.DeviceName &&
d.DeviceInfo.Model == rgbDevice.DeviceInfo.Model &&
d.DeviceInfo.Manufacturer == rgbDevice.DeviceInfo.Manufacturer)
.ToList()
.IndexOf(rgbDevice) + 1;
surface.Devices.Add(device);
}
#endregion