diff --git a/CUE.NET.csproj b/CUE.NET.csproj index b7c90ac..7d7793d 100644 --- a/CUE.NET.csproj +++ b/CUE.NET.csproj @@ -55,10 +55,13 @@ + + + diff --git a/Devices/Generic/AbstractCueDevice.cs b/Devices/Generic/AbstractCueDevice.cs index 138cb3f..40e522a 100644 --- a/Devices/Generic/AbstractCueDevice.cs +++ b/Devices/Generic/AbstractCueDevice.cs @@ -3,18 +3,40 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using CUE.NET.Devices.Generic.Enums; using CUE.NET.Native; namespace CUE.NET.Devices.Generic { public abstract class AbstractCueDevice : ICueDevice { + private UpdateMode _updateMode = UpdateMode.AutoOnEffect; + #region Properties & Fields public IDeviceInfo DeviceInfo { get; } + public UpdateMode UpdateMode + { + get { return _updateMode; } + set + { + _updateMode = value; + CheckUpdateLoop(); + } + } + public float UpdateFrequency { get; set; } = 1f / 30f; + private Dictionary Leds { get; } = new Dictionary(); + protected abstract bool HasEffect { get; } + + private CancellationTokenSource _updateTokenSource; + private CancellationToken _updateToken; + private Task _updateTask; + #endregion #region Constructors @@ -22,6 +44,8 @@ namespace CUE.NET.Devices.Generic protected AbstractCueDevice(IDeviceInfo info) { this.DeviceInfo = info; + + CheckUpdateLoop(); } #endregion @@ -36,16 +60,61 @@ namespace CUE.NET.Devices.Generic return Leds[ledId]; } - public virtual void UpdateLeds(bool forceUpdate = false) + protected async void CheckUpdateLoop() { - IList> ledsToUpdate = (forceUpdate ? Leds : Leds.Where(x => x.Value.IsDirty)).ToList(); + bool shouldRun; + switch (UpdateMode) + { + case UpdateMode.Manual: + shouldRun = false; + break; + case UpdateMode.AutoOnEffect: + shouldRun = HasEffect; + break; + case UpdateMode.Continuous: + shouldRun = true; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (shouldRun && _updateTask == null) // Start task + { + _updateTokenSource?.Dispose(); + _updateTokenSource = new CancellationTokenSource(); + _updateTask = Task.Factory.StartNew(UpdateLoop, (_updateToken = _updateTokenSource.Token)); + } + else if (!shouldRun && _updateTask != null) // Stop task + { + _updateTokenSource.Cancel(); + await _updateTask; + _updateTask.Dispose(); + _updateTask = null; + } + } + + private void UpdateLoop() + { + while (!_updateToken.IsCancellationRequested) + { + long preUpdateTicks = DateTime.Now.Ticks; + Update(); + int sleep = (int)((UpdateFrequency * 1000f) - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); + if (sleep > 0) + Thread.Sleep(sleep); + } + } + + public virtual void Update(bool flushLeds = false) + { + IList> ledsToUpdate = (flushLeds ? Leds : Leds.Where(x => x.Value.IsDirty)).ToList(); foreach (CorsairLed led in Leds.Values) led.Update(); UpdateLeds(ledsToUpdate); } - + private static void UpdateLeds(ICollection> ledsToUpdate) { ledsToUpdate = ledsToUpdate.Where(x => x.Value.Color != Color.Transparent).ToList(); diff --git a/Devices/Generic/Enums/UpdateMode.cs b/Devices/Generic/Enums/UpdateMode.cs new file mode 100644 index 0000000..8ce44ab --- /dev/null +++ b/Devices/Generic/Enums/UpdateMode.cs @@ -0,0 +1,9 @@ +namespace CUE.NET.Devices.Generic.Enums +{ + public enum UpdateMode + { + Manual, + AutoOnEffect, + Continuous + } +} diff --git a/Devices/Headset/CorsairHeadset.cs b/Devices/Headset/CorsairHeadset.cs index c754b24..a976a64 100644 --- a/Devices/Headset/CorsairHeadset.cs +++ b/Devices/Headset/CorsairHeadset.cs @@ -7,6 +7,8 @@ namespace CUE.NET.Devices.Headset { #region Properties & Fields + protected override bool HasEffect => false; + #endregion #region Constructors diff --git a/Devices/ICueDevice.cs b/Devices/ICueDevice.cs index c1bab11..4a6d654 100644 --- a/Devices/ICueDevice.cs +++ b/Devices/ICueDevice.cs @@ -1,9 +1,15 @@ -namespace CUE.NET.Devices +using CUE.NET.Devices.Generic.Enums; + +namespace CUE.NET.Devices { public interface ICueDevice { IDeviceInfo DeviceInfo { get; } - void UpdateLeds(bool forceUpdate = false); + UpdateMode UpdateMode { get; set; } + + float UpdateFrequency { get; set; } + + void Update(bool flushLeds = false); } } diff --git a/Devices/Keyboard/CorsairKeyboard.cs b/Devices/Keyboard/CorsairKeyboard.cs index 27fa6a0..14c13b4 100644 --- a/Devices/Keyboard/CorsairKeyboard.cs +++ b/Devices/Keyboard/CorsairKeyboard.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.InteropServices; using CUE.NET.Devices.Generic; using CUE.NET.Devices.Keyboard.Brushes; +using CUE.NET.Devices.Keyboard.Effects; using CUE.NET.Devices.Keyboard.Enums; using CUE.NET.Devices.Keyboard.Keys; using CUE.NET.Helper; @@ -18,11 +19,8 @@ namespace CUE.NET.Devices.Keyboard { #region Properties & Fields - public CorsairKeyboardDeviceInfo KeyboardDeviceInfo { get; } + #region Indexer - public RectangleF KeyboardRectangle { get; private set; } - - private Dictionary _keys = new Dictionary(); public CorsairKey this[CorsairKeyboardKeyId keyId] { get @@ -51,11 +49,26 @@ namespace CUE.NET.Devices.Keyboard private set { throw new NotSupportedException(); } } + #endregion + + private readonly LinkedList _keyGroups = new LinkedList(); + private readonly LinkedList _effects = new LinkedList(); + + private Dictionary _keys = new Dictionary(); public IEnumerable Keys => new ReadOnlyCollection(_keys.Values.ToList()); + public CorsairKeyboardDeviceInfo KeyboardDeviceInfo { get; } + public RectangleF KeyboardRectangle { get; private set; } public IBrush Brush { get; set; } - private readonly IList _keyGroups = new List(); + protected override bool HasEffect + { + get + { + lock (_effects) + return _effects.Any(); + } + } #endregion @@ -74,19 +87,61 @@ namespace CUE.NET.Devices.Keyboard #region Methods - public override void UpdateLeds(bool forceUpdate = false) - { - // Apply all KeyGroups + #region Update + public override void Update(bool flushLeds = false) + { + Console.WriteLine("Update"); + + UpdateKeyGroups(); + UpdateEffects(); + + // Perform 'real' update + base.Update(flushLeds); + } + + private void UpdateEffects() + { + List effectsToRemove = new List(); + lock (_effects) + { + long currentTicks = DateTime.Now.Ticks; + foreach (EffectTimeContainer effect in _effects) + { + float deltaTime; + if (effect.TicksAtLastUpdate < 0) + { + effect.TicksAtLastUpdate = currentTicks; + deltaTime = 0f; + } + else + deltaTime = (currentTicks - effect.TicksAtLastUpdate) / 10000000f; + + effect.TicksAtLastUpdate = currentTicks; + effect.Effect.Update(deltaTime); + + ApplyBrush((effect.Effect.KeyList ?? this).ToList(), effect.Effect.EffectBrush); + + if (effect.Effect.IsDone) + effectsToRemove.Add(effect.Effect); + } + } + + foreach (IEffect effect in effectsToRemove) + DetachEffect(effect); + } + + private void UpdateKeyGroups() + { if (Brush != null) ApplyBrush(this.ToList(), Brush); - //TODO DarthAffe 20.09.2015: Add some sort of priority - foreach (IKeyGroup keyGroup in _keyGroups) - ApplyBrush(keyGroup.Keys.ToList(), keyGroup.Brush); - - // Perform 'real' update - base.UpdateLeds(forceUpdate); + lock (_keyGroups) + { + //TODO DarthAffe 20.09.2015: Add some sort of priority + foreach (IKeyGroup keyGroup in _keyGroups) + ApplyBrush(keyGroup.Keys.ToList(), keyGroup.Brush); + } } private void ApplyBrush(ICollection keys, IBrush brush) @@ -96,20 +151,68 @@ namespace CUE.NET.Devices.Keyboard key.Led.Color = brush.GetColorAtPoint(brushRectangle, key.KeyRectangle.GetCenter()); } + #endregion + public bool AttachKeyGroup(IKeyGroup keyGroup) { - if (keyGroup == null || _keyGroups.Contains(keyGroup)) return false; + lock (_keyGroups) + { + if (keyGroup == null || _keyGroups.Contains(keyGroup)) return false; - _keyGroups.Add(keyGroup); - return true; + _keyGroups.AddLast(keyGroup); + return true; + } } public bool DetachKeyGroup(IKeyGroup keyGroup) { - if (keyGroup == null || !_keyGroups.Contains(keyGroup)) return false; + lock (_keyGroups) + { + if (keyGroup == null) return false; - _keyGroups.Remove(keyGroup); - return true; + LinkedListNode node = _keyGroups.Find(keyGroup); + if (node == null) return false; + + _keyGroups.Remove(node); + return true; + } + } + + public bool AttachEffect(IEffect effect) + { + bool retVal = false; + lock (_effects) + { + if (effect != null && _effects.All(x => x.Effect != effect)) + { + effect.OnAttach(); + _effects.AddLast(new EffectTimeContainer(effect, -1)); + retVal = true; + } + } + + CheckUpdateLoop(); + return retVal; + } + + public bool DetachEffect(IEffect effect) + { + bool retVal = false; + lock (_effects) + { + if (effect != null) + { + EffectTimeContainer val = _effects.FirstOrDefault(x => x.Effect == effect); + if (val != null) + { + effect.OnDetach(); + _effects.Remove(val); + retVal = true; + } + } + } + CheckUpdateLoop(); + return retVal; } private void InitializeKeys() diff --git a/Devices/Keyboard/Effects/EffectTimeContainer.cs b/Devices/Keyboard/Effects/EffectTimeContainer.cs new file mode 100644 index 0000000..22dafca --- /dev/null +++ b/Devices/Keyboard/Effects/EffectTimeContainer.cs @@ -0,0 +1,23 @@ +namespace CUE.NET.Devices.Keyboard.Effects +{ + internal class EffectTimeContainer + { + #region Properties & Fields + + internal IEffect Effect { get; set; } + + internal long TicksAtLastUpdate { get; set; } + + #endregion + + #region Constructors + + internal EffectTimeContainer(IEffect effect, long ticksAtLastUpdate) + { + Effect = effect; + TicksAtLastUpdate = ticksAtLastUpdate; + } + + #endregion + } +} diff --git a/Devices/Keyboard/Effects/IEffect.cs b/Devices/Keyboard/Effects/IEffect.cs new file mode 100644 index 0000000..9f166aa --- /dev/null +++ b/Devices/Keyboard/Effects/IEffect.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using CUE.NET.Devices.Keyboard.Brushes; +using CUE.NET.Devices.Keyboard.Keys; + +namespace CUE.NET.Devices.Keyboard.Effects +{ + public interface IEffect + { + #region Properties & Fields + + IBrush EffectBrush { get; } + + IEnumerable KeyList { get; } + + bool IsDone { get; } + + #endregion + + #region Methods + + void Update(float deltaTime); + + void OnAttach(); + + void OnDetach(); + + #endregion + } +} diff --git a/Devices/Mouse/CorsairMouse.cs b/Devices/Mouse/CorsairMouse.cs index e61cb05..e770e91 100644 --- a/Devices/Mouse/CorsairMouse.cs +++ b/Devices/Mouse/CorsairMouse.cs @@ -9,6 +9,8 @@ namespace CUE.NET.Devices.Mouse public CorsairMouseDeviceInfo MouseDeviceInfo { get; } + protected override bool HasEffect => false; + #endregion #region Constructors