diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index a3ec77f2a..d53817c8d 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -76,6 +76,7 @@ + diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index d525a9e5f..b7ccc8d46 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -56,7 +56,7 @@ namespace ArchiSteamFarm { internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet(); [JsonProperty] - internal readonly ConcurrentSortedHashSet GamesToFarm = new ConcurrentSortedHashSet(); + internal readonly ConcurrentList GamesToFarm = new ConcurrentList(); [JsonProperty] internal TimeSpan TimeRemaining => @@ -100,7 +100,6 @@ namespace ArchiSteamFarm { EventSemaphore.Dispose(); FarmingInitializationSemaphore.Dispose(); FarmingResetSemaphore.Dispose(); - GamesToFarm.Dispose(); // Those are objects that might be null and the check should be in-place IdleFarmingTimer?.Dispose(); @@ -1058,34 +1057,34 @@ namespace ArchiSteamFarm { private async Task SortGamesToFarm() { // Put priority idling appIDs on top - IOrderedEnumerable gamesToFarm = GamesToFarm.OrderByDescending(game => Bot.IsPriorityIdling(game.AppID)); + IOrderedEnumerable orderedGamesToFarm = GamesToFarm.OrderByDescending(game => Bot.IsPriorityIdling(game.AppID)); foreach (BotConfig.EFarmingOrder farmingOrder in Bot.BotConfig.FarmingOrders) { switch (farmingOrder) { case BotConfig.EFarmingOrder.Unordered: break; case BotConfig.EFarmingOrder.AppIDsAscending: - gamesToFarm = gamesToFarm.ThenBy(game => game.AppID); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.AppID); break; case BotConfig.EFarmingOrder.AppIDsDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => game.AppID); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.AppID); break; case BotConfig.EFarmingOrder.BadgeLevelsAscending: - gamesToFarm = gamesToFarm.ThenBy(game => game.BadgeLevel); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.BadgeLevel); break; case BotConfig.EFarmingOrder.BadgeLevelsDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => game.BadgeLevel); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.BadgeLevel); break; case BotConfig.EFarmingOrder.CardDropsAscending: - gamesToFarm = gamesToFarm.ThenBy(game => game.CardsRemaining); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.CardsRemaining); break; case BotConfig.EFarmingOrder.CardDropsDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => game.CardsRemaining); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.CardsRemaining); break; case BotConfig.EFarmingOrder.MarketableAscending: @@ -1095,11 +1094,11 @@ namespace ArchiSteamFarm { if ((marketableAppIDs != null) && (marketableAppIDs.Count > 0)) { switch (farmingOrder) { case BotConfig.EFarmingOrder.MarketableAscending: - gamesToFarm = gamesToFarm.ThenBy(game => marketableAppIDs.Contains(game.AppID)); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => marketableAppIDs.Contains(game.AppID)); break; case BotConfig.EFarmingOrder.MarketableDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => marketableAppIDs.Contains(game.AppID)); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => marketableAppIDs.Contains(game.AppID)); break; default: @@ -1111,23 +1110,23 @@ namespace ArchiSteamFarm { break; case BotConfig.EFarmingOrder.HoursAscending: - gamesToFarm = gamesToFarm.ThenBy(game => game.HoursPlayed); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.HoursPlayed); break; case BotConfig.EFarmingOrder.HoursDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => game.HoursPlayed); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.HoursPlayed); break; case BotConfig.EFarmingOrder.NamesAscending: - gamesToFarm = gamesToFarm.ThenBy(game => game.GameName); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => game.GameName); break; case BotConfig.EFarmingOrder.NamesDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => game.GameName); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => game.GameName); break; case BotConfig.EFarmingOrder.Random: - gamesToFarm = gamesToFarm.ThenBy(game => Utilities.RandomNext()); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => Utilities.RandomNext()); break; case BotConfig.EFarmingOrder.RedeemDateTimesAscending: @@ -1157,11 +1156,11 @@ namespace ArchiSteamFarm { switch (farmingOrder) { case BotConfig.EFarmingOrder.RedeemDateTimesAscending: - gamesToFarm = gamesToFarm.ThenBy(game => redeemDates[game.AppID]); + orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => redeemDates[game.AppID]); break; case BotConfig.EFarmingOrder.RedeemDateTimesDescending: - gamesToFarm = gamesToFarm.ThenByDescending(game => redeemDates[game.AppID]); + orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => redeemDates[game.AppID]); break; default: @@ -1178,8 +1177,11 @@ namespace ArchiSteamFarm { } } - // We must call ToList() here as we can't replace items while enumerating - GamesToFarm.ReplaceWith(gamesToFarm.ToList()); + // We must call ToList() here as we can't do in-place replace + List gamesToFarm = orderedGamesToFarm.ToList(); + + GamesToFarm.Clear(); + GamesToFarm.AddRange(gamesToFarm); } internal sealed class Game : IEquatable { diff --git a/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs b/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs index 66bc62e69..bd3f3196d 100644 --- a/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs +++ b/ArchiSteamFarm/Collections/ConcurrentEnumerator.cs @@ -22,7 +22,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Threading; using JetBrains.Annotations; namespace ArchiSteamFarm.Collections { @@ -30,22 +29,20 @@ namespace ArchiSteamFarm.Collections { public T Current => Enumerator.Current; private readonly IEnumerator Enumerator; - private readonly SemaphoreSlim Semaphore; + private readonly IDisposable Lock; object IEnumerator.Current => Current; - internal ConcurrentEnumerator([NotNull] IReadOnlyCollection collection, [NotNull] SemaphoreSlim semaphore) { - if ((collection == null) || (semaphore == null)) { - throw new ArgumentNullException(nameof(collection) + " || " + nameof(semaphore)); + internal ConcurrentEnumerator([NotNull] IReadOnlyCollection collection, [NotNull] IDisposable @lock) { + if ((collection == null) || (@lock == null)) { + throw new ArgumentNullException(nameof(collection) + " || " + nameof(@lock)); } - Semaphore = semaphore; - semaphore.Wait(); - + Lock = @lock; Enumerator = collection.GetEnumerator(); } - public void Dispose() => Semaphore.Release(); + public void Dispose() => Lock.Dispose(); public bool MoveNext() => Enumerator.MoveNext(); public void Reset() => Enumerator.Reset(); } diff --git a/ArchiSteamFarm/Collections/ConcurrentList.cs b/ArchiSteamFarm/Collections/ConcurrentList.cs new file mode 100644 index 000000000..63229ab69 --- /dev/null +++ b/ArchiSteamFarm/Collections/ConcurrentList.cs @@ -0,0 +1,122 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Nito.AsyncEx; + +namespace ArchiSteamFarm.Collections { + internal sealed class ConcurrentList : IList, IReadOnlyList { + public bool IsReadOnly => false; + + internal int Count { + get { + using (Lock.ReaderLock()) { + return BackingCollection.Count; + } + } + } + + private readonly List BackingCollection = new List(); + private readonly AsyncReaderWriterLock Lock = new AsyncReaderWriterLock(); + + int ICollection.Count => Count; + int IReadOnlyCollection.Count => Count; + + public T this[int index] { + get { + using (Lock.ReaderLock()) { + return BackingCollection[index]; + } + } + + set { + using (Lock.WriterLock()) { + BackingCollection[index] = value; + } + } + } + + public void Add(T item) { + using (Lock.WriterLock()) { + BackingCollection.Add(item); + } + } + + public void Clear() { + using (Lock.WriterLock()) { + BackingCollection.Clear(); + } + } + + public bool Contains(T item) { + using (Lock.ReaderLock()) { + return BackingCollection.Contains(item); + } + } + + public void CopyTo(T[] array, int arrayIndex) { + using (Lock.ReaderLock()) { + BackingCollection.CopyTo(array, arrayIndex); + } + } + + [NotNull] + [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")] + public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, Lock.ReaderLock()); + + public int IndexOf(T item) { + using (Lock.ReaderLock()) { + return BackingCollection.IndexOf(item); + } + } + + public void Insert(int index, T item) { + using (Lock.WriterLock()) { + BackingCollection.Insert(index, item); + } + } + + public bool Remove(T item) { + using (Lock.WriterLock()) { + return BackingCollection.Remove(item); + } + } + + public void RemoveAt(int index) { + using (Lock.WriterLock()) { + BackingCollection.RemoveAt(index); + } + } + + [NotNull] + [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")] + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal void AddRange([NotNull] IEnumerable collection) { + using (Lock.WriterLock()) { + BackingCollection.AddRange(collection); + } + } + } +} diff --git a/ArchiSteamFarm/Collections/ConcurrentSortedHashSet.cs b/ArchiSteamFarm/Collections/ConcurrentSortedHashSet.cs deleted file mode 100644 index 268cf5a37..000000000 --- a/ArchiSteamFarm/Collections/ConcurrentSortedHashSet.cs +++ /dev/null @@ -1,227 +0,0 @@ -// _ _ _ ____ _ _____ -// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ -// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ -// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | -// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// | -// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki -// Contact: JustArchi@JustArchi.net -// | -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// | -// http://www.apache.org/licenses/LICENSE-2.0 -// | -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using JetBrains.Annotations; - -namespace ArchiSteamFarm.Collections { - internal sealed class ConcurrentSortedHashSet : IDisposable, IReadOnlyCollection, ISet { - public int Count { - get { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.Count; - } finally { - CollectionSemaphore.Release(); - } - } - } - - public bool IsReadOnly => false; - - private readonly HashSet BackingCollection = new HashSet(); - private readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1); - - public bool Add(T item) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.Add(item); - } finally { - CollectionSemaphore.Release(); - } - } - - public void Clear() { - CollectionSemaphore.Wait(); - - try { - BackingCollection.Clear(); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool Contains(T item) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.Contains(item); - } finally { - CollectionSemaphore.Release(); - } - } - - [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")] - public void CopyTo([NotNull] T[] array, int arrayIndex) { - CollectionSemaphore.Wait(); - - try { - BackingCollection.CopyTo(array, arrayIndex); - } finally { - CollectionSemaphore.Release(); - } - } - - public void Dispose() => CollectionSemaphore.Dispose(); - - public void ExceptWith(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - BackingCollection.ExceptWith(other); - } finally { - CollectionSemaphore.Release(); - } - } - - [NotNull] - [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")] - public IEnumerator GetEnumerator() => new ConcurrentEnumerator(BackingCollection, CollectionSemaphore); - - public void IntersectWith(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - BackingCollection.IntersectWith(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool IsProperSubsetOf(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.IsProperSubsetOf(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool IsProperSupersetOf(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.IsProperSupersetOf(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool IsSubsetOf(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.IsSubsetOf(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool IsSupersetOf(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.IsSupersetOf(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool Overlaps(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.Overlaps(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool Remove(T item) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.Remove(item); - } finally { - CollectionSemaphore.Release(); - } - } - - public bool SetEquals(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - return BackingCollection.SetEquals(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public void SymmetricExceptWith(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - BackingCollection.SymmetricExceptWith(other); - } finally { - CollectionSemaphore.Release(); - } - } - - public void UnionWith(IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - BackingCollection.UnionWith(other); - } finally { - CollectionSemaphore.Release(); - } - } - - [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")] - [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] - void ICollection.Add([NotNull] T item) => Add(item); - - [NotNull] - [SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")] - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - internal void ReplaceWith([NotNull] IEnumerable other) { - CollectionSemaphore.Wait(); - - try { - BackingCollection.Clear(); - - foreach (T item in other) { - BackingCollection.Add(item); - } - } finally { - CollectionSemaphore.Release(); - } - } - } -}