|
| 1 | +using System.Buffers; |
| 2 | +using System.Collections; |
| 3 | +using static Fayti1703.CommonLib.Misc; |
| 4 | + |
| 5 | +namespace BinaryMatrix.Engine; |
| 6 | + |
| 7 | +/* TODO?: Do an optimization pass over this? |
| 8 | + * We could take advantage of the unique nature of literally *every single list* here. |
| 9 | + * Combat Stacks: Filled one at a time, then rapidly emptied. (Defender stack might have some leftovers.) |
| 10 | + * Lane Decks/Attacker Deck: Drained one at a time, then rapidly refilled. |
| 11 | + * Discard Piles: Filled slowly, then rapidly emptied. |
| 12 | + * |
| 13 | + * (honestly optimizing this doesn't matter except for rapidly simulating games, so this is more of an academic thing) |
| 14 | + */ |
| 15 | +/* Note on ordering: For optimization purposes, */ |
| 16 | +public sealed class CardList : IEnumerable<Card>, IDisposable { |
| 17 | + |
| 18 | + private const bool FAIL_FAST_INVALID = true; |
| 19 | + |
| 20 | + private Card[] cards = Array.Empty<Card>(); |
| 21 | + public int Count { get; private set; } = 0; |
| 22 | + |
| 23 | + /* TODO?: Profile array pool usage to get a sense of some better parameters here. */ |
| 24 | + private static readonly ArrayPool<Card> listPool = ArrayPool<Card>.Create(8, 32); |
| 25 | + |
| 26 | + public CardList() {} |
| 27 | + |
| 28 | + public CardList(int initialCapacity) { |
| 29 | + this.cards = listPool.Rent(initialCapacity); |
| 30 | + } |
| 31 | + |
| 32 | + public CardList(IEnumerable<Card> cards) { |
| 33 | + if(cards is CardList list) { |
| 34 | + this.cards = listPool.Rent(list.Count); |
| 35 | + this.AddAll(list); |
| 36 | + return; |
| 37 | + } |
| 38 | + |
| 39 | + Card[] theCards = cards.ToArray(); |
| 40 | + this.cards = listPool.Rent(theCards.Length); |
| 41 | + Array.Copy(theCards, this.cards, theCards.Length); |
| 42 | + this.Count = theCards.Length; |
| 43 | + if(FAIL_FAST_INVALID) { |
| 44 | + foreach(ref Card card in this) { |
| 45 | + if(card.IsInvalid) |
| 46 | + throw new ArgumentException("The provided enumerable contains an invalid card!", nameof(cards)); |
| 47 | + } |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + public CardList(CardList cards) { |
| 52 | + this.cards = listPool.Rent(cards.Count); |
| 53 | + this.AddAll(cards); |
| 54 | + } |
| 55 | + |
| 56 | + public void Dispose() { |
| 57 | + if(this.cards.Length > 0) |
| 58 | + listPool.Return(this.cards); |
| 59 | + } |
| 60 | + |
| 61 | + public ref Card this[int index] { |
| 62 | + get { |
| 63 | + if(index < 0 || index >= this.Count) |
| 64 | + throw new IndexOutOfRangeException(); |
| 65 | + return ref this.cards[index]; |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + public ref Card Add(Card card) { |
| 70 | + if(FAIL_FAST_INVALID && card.IsInvalid) |
| 71 | + throw new ArgumentException("Invalid card inserted!", nameof(card)); |
| 72 | + this.EnsureFreeCapacity(1); |
| 73 | + this.cards[this.Count] = card; |
| 74 | + this.Count++; |
| 75 | + return ref this.cards[this.Count - 1]; |
| 76 | + } |
| 77 | + |
| 78 | + public void AddAll(CardList cards) { |
| 79 | + this.EnsureFreeCapacity(cards.Count); |
| 80 | + |
| 81 | + Span<Card> sourceRange = cards.cards.AsSpan(..cards.Count); |
| 82 | + |
| 83 | + if(FAIL_FAST_INVALID) { |
| 84 | + /* TODO: Can this even happen? */ |
| 85 | + foreach(ref Card card in sourceRange) { |
| 86 | + if(card.IsInvalid) |
| 87 | + throw new ArgumentException("The provided list contains an invalid card!", nameof(cards)); |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + sourceRange.CopyTo(this.cards.AsSpan(this.Count..)); |
| 92 | + this.Count += cards.Count; |
| 93 | + } |
| 94 | + |
| 95 | + public void Clear() { |
| 96 | + listPool.Return(Exchange(ref this.cards, Array.Empty<Card>())); |
| 97 | + this.Count = 0; |
| 98 | + } |
| 99 | + |
| 100 | + public OptionalRef<Card> First() { |
| 101 | + return this.Count == 0 ? OptionalRef<Card>.Empty : new OptionalRef<Card>(ref this[0]); |
| 102 | + } |
| 103 | + |
| 104 | + public OptionalRef<Card> Last() { |
| 105 | + return this.Count == 0 ? OptionalRef<Card>.Empty : new OptionalRef<Card>(ref this[^1]); |
| 106 | + } |
| 107 | + |
| 108 | + private void EnsureFreeCapacity(int requested) { |
| 109 | + int requiredLength = requested + this.Count; |
| 110 | + |
| 111 | + if(requiredLength < this.cards.Length) |
| 112 | + return; |
| 113 | + |
| 114 | + int newLength; |
| 115 | + do { |
| 116 | + newLength = this.cards.Length * 2; |
| 117 | + if(newLength == 0) newLength = 1; |
| 118 | + } while(requiredLength > newLength); |
| 119 | + |
| 120 | + Card[] aNewArray = listPool.Rent(newLength); |
| 121 | + this.cards.CopyTo(aNewArray, 0); |
| 122 | + Card[] theOldArray = Exchange(ref this.cards, aNewArray); /* !! REF INVALIDATION HERE !! */ |
| 123 | + listPool.Return(theOldArray); |
| 124 | + } |
| 125 | + |
| 126 | + public CardListEnumerator GetEnumerator() => new(this); |
| 127 | + IEnumerator<Card> IEnumerable<Card>.GetEnumerator() => this.GetEnumerator(); |
| 128 | + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); |
| 129 | + |
| 130 | + |
| 131 | + public void MoveAllTo(CardList cards) { |
| 132 | + if(cards.Count == 0) { |
| 133 | + /* target list is empty -> we can simply donate our array to it */ |
| 134 | + cards.cards = this.cards; |
| 135 | + cards.Count = this.Count; |
| 136 | + this.cards = Array.Empty<Card>(); |
| 137 | + this.Count = 0; |
| 138 | + } else { |
| 139 | + /* otherwise -> slow track, add all of our stuff, then clear ourselves */ |
| 140 | + cards.AddAll(this); |
| 141 | + this.Clear(); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + public Card? TakeFirst() { |
| 146 | + if(this.Count <= 0) |
| 147 | + return null; |
| 148 | + |
| 149 | + Card ret = this.cards[0]; |
| 150 | + this.Count--; |
| 151 | + if(this.Count != 0) |
| 152 | + Array.Copy(this.cards, 1, this.cards, 0, this.Count); |
| 153 | + return ret; |
| 154 | + } |
| 155 | + |
| 156 | + public Card? TakeLast() { |
| 157 | + if(this.Count <= 0) |
| 158 | + return null; |
| 159 | + Card ret = this.cards[this.Count - 1]; |
| 160 | + this.Count--; |
| 161 | + return ret; |
| 162 | + } |
| 163 | + |
| 164 | + public Card? Take(int index) { |
| 165 | + Card ret = this.cards[index]; |
| 166 | + this.Count--; |
| 167 | + if(index < this.Count) |
| 168 | + Array.Copy(this.cards, index + 1, this.cards, index, this.Count - index); |
| 169 | + return ret; |
| 170 | + } |
| 171 | + |
| 172 | + public class CardListEnumerator : IEnumerator<Card> { |
| 173 | + private readonly CardList list; |
| 174 | + private int current = -1; |
| 175 | + |
| 176 | + public CardListEnumerator(CardList list) { |
| 177 | + this.list = list; |
| 178 | + } |
| 179 | + |
| 180 | + public bool MoveNext() { |
| 181 | + if(this.current >= this.list.Count) |
| 182 | + return false; |
| 183 | + this.current++; |
| 184 | + return this.current < this.list.Count; |
| 185 | + } |
| 186 | + |
| 187 | + public void Reset() { |
| 188 | + this.current = -1; |
| 189 | + } |
| 190 | + |
| 191 | + public ref Card Current => ref this.list[this.current]; |
| 192 | + Card IEnumerator<Card>.Current => this.Current; |
| 193 | + object IEnumerator.Current => this.Current; |
| 194 | + |
| 195 | + public void Dispose() { /* nothing to dispose */ } |
| 196 | + } |
| 197 | +} |
0 commit comments