// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMethodReturnValue.Global
using System;
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.Devices.Generic.EventArgs;
using CUE.NET.Native;
namespace CUE.NET.Devices.Generic
{
///
/// Represents a generic CUE-device. (keyboard, mouse, headset, ...)
///
public abstract class AbstractCueDevice : ICueDevice
{
#region Properties & Fields
///
/// Gets generic information provided by CUE for the device.
///
public IDeviceInfo DeviceInfo { get; }
private UpdateMode _updateMode = UpdateMode.Manual;
///
/// Gets or sets the update-mode for the device.
///
public UpdateMode UpdateMode
{
get { return _updateMode; }
set
{
_updateMode = value;
CheckUpdateLoop();
}
}
///
/// Gets or sets the update-frequency in seconds. (Calculate by using '1f / updates per second')
///
public float UpdateFrequency { get; set; } = 1f / 30f;
///
/// Gets a dictionary containing all LEDs of the device.
///
protected Dictionary Leds { get; } = new Dictionary();
private CancellationTokenSource _updateTokenSource;
private CancellationToken _updateToken;
private Task _updateTask;
private DateTime _lastUpdate = DateTime.Now;
#endregion
#region Events
///
/// Occurs when a catched exception is thrown inside the device.
///
public event ExceptionEventHandler Exception;
///
/// Occurs when the device starts updating.
///
public event UpdatingEventHandler Updating;
///
/// Occurs when the device update is done.
///
public event UpdatedEventHandler Updated;
///
/// Occurs when the device starts to update the leds.
///
public event LedsUpdatingEventHandler LedsUpdating;
///
/// Occurs when the device updated the leds.
///
public event LedsUpdatedEventHandler LedsUpdated;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The generic information provided by CUE for the device.
protected AbstractCueDevice(IDeviceInfo info)
{
this.DeviceInfo = info;
CheckUpdateLoop();
}
#endregion
#region Methods
///
/// Gets the LED-Object with the specified id.
///
/// The LED-Id to look for.
///
protected CorsairLed GetLed(int ledId)
{
if (!Leds.ContainsKey(ledId))
Leds.Add(ledId, new CorsairLed());
return Leds[ledId];
}
///
/// Checks if automatic updates should occur and starts/stops the update-loop if needed.
///
/// Thrown if the requested update-mode is not available.
protected async void CheckUpdateLoop()
{
bool shouldRun;
switch (UpdateMode)
{
case UpdateMode.Manual:
shouldRun = false;
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);
}
}
///
/// Performs an update for all dirty keys, or all keys if flushLeds is set to true.
///
/// Specifies whether all keys (including clean ones) should be updated.
public void Update(bool flushLeds = false)
{
OnUpdating();
DeviceUpdateEffects();
DeviceUpdate();
ICollection ledsToUpdate = (flushLeds ? Leds : Leds.Where(x => x.Value.IsDirty)).Select(x => new LedUpateRequest(x.Key, x.Value.RequestedColor)).ToList();
foreach (CorsairLed led in Leds.Values)
led.Update();
UpdateLeds(ledsToUpdate);
OnUpdated();
}
///
/// Performs device specific updates.
///
protected abstract void DeviceUpdate();
///
/// Performs device specific updates effect-updates.
///
protected abstract void DeviceUpdateEffects();
private void UpdateLeds(ICollection updateRequests)
{
updateRequests = updateRequests.Where(x => x.Color != Color.Transparent).ToList();
OnLedsUpdating(updateRequests);
if (updateRequests.Any()) // CUE seems to crash if 'CorsairSetLedsColors' is called with a zero length array
{
int structSize = Marshal.SizeOf(typeof(_CorsairLedColor));
IntPtr ptr = Marshal.AllocHGlobal(structSize * updateRequests.Count);
IntPtr addPtr = new IntPtr(ptr.ToInt64());
foreach (LedUpateRequest ledUpdateRequest in updateRequests)
{
_CorsairLedColor color = new _CorsairLedColor
{
ledId = ledUpdateRequest.LedId,
r = ledUpdateRequest.Color.R,
g = ledUpdateRequest.Color.G,
b = ledUpdateRequest.Color.B
};
Marshal.StructureToPtr(color, addPtr, false);
addPtr = new IntPtr(addPtr.ToInt64() + structSize);
}
_CUESDK.CorsairSetLedsColors(updateRequests.Count, ptr);
Marshal.FreeHGlobal(ptr);
}
OnLedsUpdated(updateRequests);
}
///
/// Resets all loaded LEDs back to default.
///
internal void ResetLeds()
{
foreach (CorsairLed led in Leds.Values)
led.Reset();
}
#region EventCaller
///
/// Handles the needed event-calls for an exception.
///
/// The exception previously thrown.
protected virtual void OnException(Exception ex)
{
try
{
Exception?.Invoke(this, new ExceptionEventArgs(ex));
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls before updating.
///
protected virtual void OnUpdating()
{
try
{
long lastUpdateTicks = _lastUpdate.Ticks;
_lastUpdate = DateTime.Now;
Updating?.Invoke(this, new UpdatingEventArgs((float)((DateTime.Now.Ticks - lastUpdateTicks) / 10000000f)));
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls after an update.
///
protected virtual void OnUpdated()
{
try
{
Updated?.Invoke(this, new UpdatedEventArgs());
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls before the leds are updated.
///
protected virtual void OnLedsUpdating(ICollection updatingLeds)
{
try
{
LedsUpdating?.Invoke(this, new LedsUpdatingEventArgs(updatingLeds));
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls after the leds are updated.
///
protected virtual void OnLedsUpdated(IEnumerable updatedLeds)
{
try
{
LedsUpdated?.Invoke(this, new LedsUpdatedEventArgs(updatedLeds));
}
catch
{
// Well ... that's not my fault
}
}
#endregion
#endregion
}
}