mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Scripting - Core implementation (#629)
Scripting - Added plugin classes Layers - Fix certain blend modes not working as intended UI - Add customizable header per page Profile editor - Hide the regular header Profile editor - Added a new toolbar
This commit is contained in:
1 parent
d749ab5bbd
commit
048864fe78
47 files changed
+1599
-237
No files matched your search
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.Ninject;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Storage;
|
||||
using HidSharp;
|
||||
using Ninject;
|
||||
@@ -29,6 +30,7 @@ namespace Artemis.Core.Services
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly IModuleService _moduleService;
|
||||
private readonly IScriptingService _scriptingService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly List<Exception> _updateExceptions = new();
|
||||
private DateTime _lastExceptionLog;
|
||||
@@ -41,7 +43,8 @@ namespace Artemis.Core.Services
|
||||
IPluginManagementService pluginManagementService,
|
||||
IRgbService rgbService,
|
||||
IProfileService profileService,
|
||||
IModuleService moduleService)
|
||||
IModuleService moduleService,
|
||||
IScriptingService scriptingService)
|
||||
{
|
||||
Kernel = kernel;
|
||||
Constants.CorePlugin.Kernel = kernel;
|
||||
@@ -51,10 +54,11 @@ namespace Artemis.Core.Services
|
||||
_rgbService = rgbService;
|
||||
_profileService = profileService;
|
||||
_moduleService = moduleService;
|
||||
_scriptingService = scriptingService;
|
||||
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
|
||||
_frameStopWatch = new Stopwatch();
|
||||
StartupArguments = new List<string>();
|
||||
|
||||
|
||||
_rgbService.IsRenderPaused = true;
|
||||
_rgbService.Surface.Updating += SurfaceOnUpdating;
|
||||
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
|
||||
@@ -107,6 +111,9 @@ namespace Artemis.Core.Services
|
||||
{
|
||||
_frameStopWatch.Restart();
|
||||
|
||||
foreach (GlobalScript script in _scriptingService.GlobalScripts)
|
||||
script.OnCoreUpdating(args.DeltaTime);
|
||||
|
||||
_moduleService.UpdateActiveModules(args.DeltaTime);
|
||||
SKTexture texture = _rgbService.OpenRender();
|
||||
SKCanvas canvas = texture.Surface.Canvas;
|
||||
@@ -126,6 +133,9 @@ namespace Artemis.Core.Services
|
||||
canvas.Flush();
|
||||
|
||||
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
|
||||
|
||||
foreach (GlobalScript script in _scriptingService.GlobalScripts)
|
||||
script.OnCoreUpdated(args.DeltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
internal class ScriptingService : IScriptingService
|
||||
{
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private List<ScriptingProvider> _scriptingProviders;
|
||||
|
||||
public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
|
||||
{
|
||||
_pluginManagementService = pluginManagementService;
|
||||
|
||||
InternalGlobalScripts = new List<GlobalScript>();
|
||||
|
||||
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
|
||||
|
||||
// No need to sub to Deactivated, scripts will deactivate themselves
|
||||
profileService.ProfileActivated += ProfileServiceOnProfileActivated;
|
||||
|
||||
foreach (ProfileConfiguration profileConfiguration in profileService.ProfileConfigurations)
|
||||
{
|
||||
if (profileConfiguration.Profile != null)
|
||||
InitializeProfileScripts(profileConfiguration.Profile);
|
||||
}
|
||||
}
|
||||
|
||||
internal List<GlobalScript> InternalGlobalScripts { get; }
|
||||
|
||||
private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value)
|
||||
{
|
||||
// Limit to one constructor, there's no need to have more and it complicates things anyway
|
||||
ConstructorInfo[] constructors = scriptType.GetConstructors();
|
||||
if (constructors.Length != 1)
|
||||
throw new ArtemisCoreException("Scripts must have exactly one constructor");
|
||||
|
||||
// Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure
|
||||
ParameterInfo configurationParameter = constructors.First().GetParameters().First(p => value.GetType().IsAssignableFrom(p.ParameterType));
|
||||
|
||||
if (configurationParameter.Name == null)
|
||||
throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {scriptType.Name} with type {value.GetType().Name}");
|
||||
return new ConstructorArgument(configurationParameter.Name, value);
|
||||
}
|
||||
|
||||
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
|
||||
{
|
||||
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
|
||||
}
|
||||
|
||||
private void ProfileServiceOnProfileActivated(object? sender, ProfileConfigurationEventArgs e)
|
||||
{
|
||||
if (e.ProfileConfiguration.Profile != null)
|
||||
InitializeProfileScripts(e.ProfileConfiguration.Profile);
|
||||
}
|
||||
|
||||
private void InitializeProfileScripts(Profile profile)
|
||||
{
|
||||
// Initialize the scripts on the profile
|
||||
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null))
|
||||
CreateScriptInstance(profile, scriptConfiguration);
|
||||
|
||||
foreach (Layer layer in profile.GetAllLayers())
|
||||
{
|
||||
// Initialize the scripts on the layers
|
||||
foreach (ScriptConfiguration scriptConfiguration in layer.ScriptConfigurations.Where(c => c.Script == null))
|
||||
CreateScriptInstance(layer, scriptConfiguration);
|
||||
|
||||
// Initialize the scripts on the layer properties of layers
|
||||
foreach (ILayerProperty layerProperty in layer.GetAllLayerProperties())
|
||||
{
|
||||
foreach (ScriptConfiguration scriptConfiguration in layerProperty.ScriptConfigurations.Where(c => c.Script == null))
|
||||
CreateScriptInstance(layerProperty, scriptConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Folder folder in profile.GetAllFolders())
|
||||
{
|
||||
// Initialize the scripts on the layer properties of folders
|
||||
foreach (ILayerProperty layerProperty in folder.GetAllLayerProperties())
|
||||
{
|
||||
foreach (ScriptConfiguration scriptConfiguration in layerProperty.ScriptConfigurations.Where(c => c.Script == null))
|
||||
CreateScriptInstance(layerProperty, scriptConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<GlobalScript> GlobalScripts => InternalGlobalScripts.AsReadOnly();
|
||||
|
||||
public GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (scriptConfiguration.Script != null)
|
||||
throw new ArtemisCoreException("The provided script configuration already has an active script");
|
||||
|
||||
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
|
||||
if (provider == null)
|
||||
return null;
|
||||
|
||||
GlobalScript script = (GlobalScript) provider.Plugin.Kernel!.Get(
|
||||
provider.GlobalScriptType,
|
||||
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
|
||||
);
|
||||
|
||||
script.ScriptingProvider = provider;
|
||||
script.ScriptingService = this;
|
||||
provider.InternalScripts.Add(script);
|
||||
InternalGlobalScripts.Add(script);
|
||||
return script;
|
||||
}
|
||||
|
||||
public ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (scriptConfiguration.Script != null)
|
||||
throw new ArtemisCoreException("The provided script configuration already has an active script");
|
||||
|
||||
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
|
||||
if (provider == null)
|
||||
return null;
|
||||
|
||||
ProfileScript script = (ProfileScript) provider.Plugin.Kernel!.Get(
|
||||
provider.ProfileScriptType,
|
||||
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
|
||||
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
|
||||
);
|
||||
|
||||
script.ScriptingProvider = provider;
|
||||
provider.InternalScripts.Add(script);
|
||||
return script;
|
||||
}
|
||||
|
||||
public LayerScript? CreateScriptInstance(Layer layer, ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (scriptConfiguration.Script != null)
|
||||
throw new ArtemisCoreException("The provided script configuration already has an active script");
|
||||
|
||||
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
|
||||
if (provider == null)
|
||||
return null;
|
||||
|
||||
LayerScript script = (LayerScript) provider.Plugin.Kernel!.Get(
|
||||
provider.LayerScriptType,
|
||||
CreateScriptConstructorArgument(provider.LayerScriptType, layer),
|
||||
CreateScriptConstructorArgument(provider.LayerScriptType, scriptConfiguration)
|
||||
);
|
||||
|
||||
script.ScriptingProvider = provider;
|
||||
provider.InternalScripts.Add(script);
|
||||
return script;
|
||||
}
|
||||
|
||||
public PropertyScript? CreateScriptInstance(ILayerProperty layerProperty, ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (scriptConfiguration.Script != null)
|
||||
throw new ArtemisCoreException("The provided script configuration already has an active script");
|
||||
|
||||
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
|
||||
if (provider == null)
|
||||
return null;
|
||||
|
||||
PropertyScript script = (PropertyScript) provider.Plugin.Kernel!.Get(
|
||||
provider.PropertyScriptType,
|
||||
CreateScriptConstructorArgument(provider.PropertyScriptType, layerProperty),
|
||||
CreateScriptConstructorArgument(provider.PropertyScriptType, scriptConfiguration)
|
||||
);
|
||||
|
||||
script.ScriptingProvider = provider;
|
||||
provider.InternalScripts.Add(script);
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IScriptingService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a read only collection of all active global scripts
|
||||
/// </summary>
|
||||
ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create an instance of a global script configured in the provided <see cref="ScriptConfiguration" />
|
||||
/// </summary>
|
||||
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
|
||||
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
|
||||
GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create an instance of a profile script configured in the provided <see cref="ScriptConfiguration" />
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile the script is bound to</param>
|
||||
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
|
||||
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
|
||||
ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create an instance of a layer script configured in the provided <see cref="ScriptConfiguration" />
|
||||
/// </summary>
|
||||
/// <param name="layer">The layer the script is bound to</param>
|
||||
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
|
||||
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
|
||||
LayerScript? CreateScriptInstance(Layer layer, ScriptConfiguration scriptConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create an instance of a layer property script configured in the provided
|
||||
/// <see cref="ScriptConfiguration" />
|
||||
/// </summary>
|
||||
/// <param name="layerProperty">The layer property the script is bound to</param>
|
||||
/// <param name="scriptConfiguration">The script configuration of the script to instantiate</param>
|
||||
/// <returns>An instance of the script if the script provider was found; otherwise <see langword="null" /></returns>
|
||||
PropertyScript? CreateScriptInstance(ILayerProperty layerProperty, ScriptConfiguration scriptConfiguration);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -158,5 +159,15 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
/// <param name="canvas"></param>
|
||||
void RenderProfiles(SKCanvas canvas);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a profile has been activated
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a profile has been deactivated
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
|
||||
}
|
||||
}
|
||||
@@ -317,6 +317,8 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
profileConfiguration.Profile = profile;
|
||||
|
||||
OnProfileActivated(new ProfileConfigurationEventArgs(profileConfiguration));
|
||||
return profile;
|
||||
}
|
||||
|
||||
@@ -330,6 +332,8 @@ namespace Artemis.Core.Services
|
||||
Profile profile = profileConfiguration.Profile;
|
||||
profileConfiguration.Profile = null;
|
||||
profile.Dispose();
|
||||
|
||||
OnProfileDeactivated(new ProfileConfigurationEventArgs(profileConfiguration));
|
||||
}
|
||||
|
||||
public void DeleteProfile(ProfileConfiguration profileConfiguration)
|
||||
@@ -582,5 +586,22 @@ namespace Artemis.Core.Services
|
||||
|
||||
_profileRepository.Save(profile.ProfileEntity);
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
|
||||
|
||||
protected virtual void OnProfileActivated(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileActivated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnProfileDeactivated(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileDeactivated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user