diff --git a/BetterJoyForCemu/BetterJoy.csproj b/BetterJoyForCemu/BetterJoy.csproj index 22fb2e8..5840bd7 100644 --- a/BetterJoyForCemu/BetterJoy.csproj +++ b/BetterJoyForCemu/BetterJoy.csproj @@ -130,6 +130,7 @@ 3rdPartyControllers.cs + diff --git a/BetterJoyForCemu/Collections/ConcurrentList.cs b/BetterJoyForCemu/Collections/ConcurrentList.cs new file mode 100644 index 0000000..fc950a3 --- /dev/null +++ b/BetterJoyForCemu/Collections/ConcurrentList.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace BetterJoyForCemu.Collections { + + // https://codereview.stackexchange.com/a/125341 + public class ConcurrentList : IList { + #region Fields + + private IList _internalList; + + private readonly object lockObject = new object(); + + #endregion + + #region ctor + + public ConcurrentList() { + _internalList = new List(); + } + + public ConcurrentList(int capacity) { + _internalList = new List(capacity); + } + + public ConcurrentList(IEnumerable list) { + _internalList = new List(); + foreach (T item in list) { + _internalList.Add(item); + } + } + + #endregion + + public T this[int index] { + get { + return LockInternalListAndGet(l => l[index]); + } + + set { + LockInternalListAndCommand(l => l[index] = value); + } + } + + public int Count { + get { + return LockInternalListAndQuery(l => l.Count); + } + } + + public bool IsReadOnly => false; + + public void Add(T item) { + LockInternalListAndCommand(l => l.Add(item)); + } + + public void Clear() { + LockInternalListAndCommand(l => l.Clear()); + } + + public bool Contains(T item) { + return LockInternalListAndQuery(l => l.Contains(item)); + } + + public void CopyTo(T[] array, int arrayIndex) { + LockInternalListAndCommand(l => l.CopyTo(array, arrayIndex)); + } + + public IEnumerator GetEnumerator() { + return LockInternalListAndQuery(l => l.GetEnumerator()); + } + + public int IndexOf(T item) { + return LockInternalListAndQuery(l => l.IndexOf(item)); + } + + public void Insert(int index, T item) { + LockInternalListAndCommand(l => l.Insert(index, item)); + } + + public bool Remove(T item) { + return LockInternalListAndQuery(l => l.Remove(item)); + } + + public void RemoveAt(int index) { + LockInternalListAndCommand(l => l.RemoveAt(index)); + } + + IEnumerator IEnumerable.GetEnumerator() { + return LockInternalListAndQuery(l => l.GetEnumerator()); + } + + #region Utilities + + protected virtual void LockInternalListAndCommand(Action> action) { + lock (lockObject) { + action(_internalList); + } + } + + protected virtual T LockInternalListAndGet(Func, T> func) { + lock (lockObject) { + return func(_internalList); + } + } + + protected virtual TObject LockInternalListAndQuery(Func, TObject> query) { + lock (lockObject) { + return query(_internalList); + } + } + + #endregion + } +} diff --git a/BetterJoyForCemu/Joycon.cs b/BetterJoyForCemu/Joycon.cs index 0ef2866..684370d 100644 --- a/BetterJoyForCemu/Joycon.cs +++ b/BetterJoyForCemu/Joycon.cs @@ -17,7 +17,7 @@ namespace BetterJoyForCemu { public bool isPro = false; public bool isSnes = false; bool isUSB = false; - public Joycon other; + public Joycon other = null; public bool active_gyro = false; private long inactivity = Stopwatch.GetTimestamp(); diff --git a/BetterJoyForCemu/Program.cs b/BetterJoyForCemu/Program.cs index 22bb30e..f96faa0 100644 --- a/BetterJoyForCemu/Program.cs +++ b/BetterJoyForCemu/Program.cs @@ -12,8 +12,8 @@ using System.ServiceProcess; using System.Text; using System.Threading; using System.Timers; -using System.Web.Configuration; using System.Windows.Forms; +using BetterJoyForCemu.Collections; using Nefarius.ViGEm.Client; using static BetterJoyForCemu._3rdPartyControllers; using static BetterJoyForCemu.HIDapi; @@ -29,7 +29,7 @@ namespace BetterJoyForCemu { private const ushort product_pro = 0x2009; private const ushort product_snes = 0x2017; - public List j; // Array of all connected Joy-Cons + public ConcurrentList j { get; private set; } // Array of all connected Joy-Cons static JoyconManager instance; public MainForm form; @@ -42,7 +42,7 @@ namespace BetterJoyForCemu { public void Awake() { instance = this; - j = new List(); + j = new ConcurrentList(); HIDapi.hid_init(); } @@ -61,16 +61,16 @@ namespace BetterJoyForCemu { void CleanUp() { // removes dropped controllers from list List rem = new List(); - for (int i = 0; i < j.Count; i++) { - Joycon v = j[i]; - if (v.state == Joycon.state_.DROPPED) { - if (v.other != null) - v.other.other = null; // The other of the other is the joycon itself + foreach (Joycon joycon in j) { + if (joycon.state == Joycon.state_.DROPPED) { + if (joycon.other != null) + joycon.other.other = null; // The other of the other is the joycon itself - v.Detach(true); rem.Add(v); + joycon.Detach(true); + rem.Add(joycon); foreach (Button b in form.con) { - if (b.Enabled & b.Tag == v) { + if (b.Enabled & b.Tag == joycon) { b.Invoke(new MethodInvoker(delegate { b.BackColor = System.Drawing.Color.FromArgb(0x00, System.Drawing.SystemColors.Control); b.Enabled = false; @@ -253,51 +253,59 @@ namespace BetterJoyForCemu { if (foundNew) { // attempt to auto join-up joycons on connection Joycon temp = null; foreach (Joycon v in j) { - if (!v.isPro) { - if (temp == null) - temp = v; - else if (temp.isLeft != v.isLeft && v.other == null) { - temp.other = v; - v.other = temp; + // Do not attach two controllers if they are either: + // - Not a Joycon + // - Already attached to another Joycon (that isn't itself) + if (v.isPro || (v.other != null && v.other != v)) { + continue; + } - //Set both Joycon LEDs to the one with the lowest ID - byte led = Math.Min(temp.LED, v.LED); - temp.LED = led; - v.LED = led; - temp.SetPlayerLED(led); - v.SetPlayerLED(led); + // Otherwise, iterate through and find the Joycon with the lowest + // id that has not been attached already (Does not include self) + if (temp == null) + temp = v; + else if (temp.isLeft != v.isLeft && v.other == null) { + temp.other = v; + v.other = temp; - if (temp.out_xbox != null) { - try { - temp.out_xbox.Disconnect(); - } catch (Exception e) { - // it wasn't connected in the first place, go figure - } + //Set both Joycon LEDs to the one with the lowest ID + byte led = Math.Min(temp.LED, v.LED); + temp.LED = led; + v.LED = led; + temp.SetPlayerLED(led); + v.SetPlayerLED(led); + + if (temp.out_xbox != null) { + try { + temp.out_xbox.Disconnect(); + } catch (Exception e) { + // it wasn't connected in the first place, go figure } - if (temp.out_ds4 != null) { - try { - temp.out_ds4.Disconnect(); - } catch (Exception e) { - // it wasn't connected in the first place, go figure - } - } - temp.out_xbox = null; - temp.out_ds4 = null; - - foreach (Button b in form.con) - if (b.Tag == v || b.Tag == temp) { - Joycon tt = (b.Tag == v) ? v : (b.Tag == temp) ? temp : v; - b.BackgroundImage = tt.isLeft ? Properties.Resources.jc_left : Properties.Resources.jc_right; - } - - temp = null; // repeat } + if (temp.out_ds4 != null) { + try { + temp.out_ds4.Disconnect(); + } catch (Exception e) { + // it wasn't connected in the first place, go figure + } + } + temp.out_xbox = null; + temp.out_ds4 = null; + + foreach (Button b in form.con) + if (b.Tag == v || b.Tag == temp) { + Joycon tt = (b.Tag == v) ? v : (b.Tag == temp) ? temp : v; + b.BackgroundImage = tt.isLeft ? Properties.Resources.jc_left : Properties.Resources.jc_right; + } + + temp = null; // repeat } } } HIDapi.hid_free_enumeration(top_ptr); + bool on = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).AppSettings.Settings["HomeLEDOn"].Value.ToLower() == "true"; foreach (Joycon jc in j) { // Connect device straight away if (jc.state == Joycon.state_.NOT_ATTACHED) { if (jc.out_xbox != null) @@ -312,10 +320,7 @@ namespace BetterJoyForCemu { continue; } - bool on = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).AppSettings.Settings["HomeLEDOn"].Value.ToLower() == "true"; - foreach (Joycon j in Program.mgr.j) { - j.SetHomeLight(on); - } + jc.SetHomeLight(on); jc.Begin(); if (form.allowCalibration) { diff --git a/BetterJoyForCemu/UpdServer.cs b/BetterJoyForCemu/UpdServer.cs index e5ea4c4..4b27ab8 100644 --- a/BetterJoyForCemu/UpdServer.cs +++ b/BetterJoyForCemu/UpdServer.cs @@ -17,7 +17,7 @@ namespace BetterJoyForCemu { public MainForm form; - public UdpServer(List p) { + public UdpServer(IList p) { controllers = p; }