SyncList.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Mirror
  5. {
  6. // Deprecated 2020-10-02
  7. [Obsolete("Use SyncList<string> instead")]
  8. public class SyncListString : SyncList<string> {}
  9. // Deprecated 2020-10-02
  10. [Obsolete("Use SyncList<float> instead")]
  11. public class SyncListFloat : SyncList<float> {}
  12. // Deprecated 2020-10-02
  13. [Obsolete("Use SyncList<int> instead")]
  14. public class SyncListInt : SyncList<int> {}
  15. // Deprecated 2020-10-02
  16. [Obsolete("Use SyncList<uint> instead")]
  17. public class SyncListUInt : SyncList<uint> {}
  18. // Deprecated 2020-10-02
  19. [Obsolete("Use SyncList<bool> instead")]
  20. public class SyncListBool : SyncList<bool> {}
  21. public class SyncList<T> : IList<T>, IReadOnlyList<T>, SyncObject
  22. {
  23. public delegate void SyncListChanged(Operation op, int itemIndex, T oldItem, T newItem);
  24. readonly IList<T> objects;
  25. readonly IEqualityComparer<T> comparer;
  26. public int Count => objects.Count;
  27. public bool IsReadOnly { get; private set; }
  28. public event SyncListChanged Callback;
  29. public enum Operation : byte
  30. {
  31. OP_ADD,
  32. OP_CLEAR,
  33. OP_INSERT,
  34. OP_REMOVEAT,
  35. OP_SET
  36. }
  37. struct Change
  38. {
  39. internal Operation operation;
  40. internal int index;
  41. internal T item;
  42. }
  43. readonly List<Change> changes = new List<Change>();
  44. // how many changes we need to ignore
  45. // this is needed because when we initialize the list,
  46. // we might later receive changes that have already been applied
  47. // so we need to skip them
  48. int changesAhead;
  49. public SyncList() : this(EqualityComparer<T>.Default) {}
  50. public SyncList(IEqualityComparer<T> comparer)
  51. {
  52. this.comparer = comparer ?? EqualityComparer<T>.Default;
  53. objects = new List<T>();
  54. }
  55. public SyncList(IList<T> objects, IEqualityComparer<T> comparer = null)
  56. {
  57. this.comparer = comparer ?? EqualityComparer<T>.Default;
  58. this.objects = objects;
  59. }
  60. public bool IsDirty => changes.Count > 0;
  61. // throw away all the changes
  62. // this should be called after a successful sync
  63. public void Flush() => changes.Clear();
  64. public void Reset()
  65. {
  66. IsReadOnly = false;
  67. changes.Clear();
  68. changesAhead = 0;
  69. objects.Clear();
  70. }
  71. void AddOperation(Operation op, int itemIndex, T oldItem, T newItem)
  72. {
  73. if (IsReadOnly)
  74. {
  75. throw new InvalidOperationException("Synclists can only be modified at the server");
  76. }
  77. Change change = new Change
  78. {
  79. operation = op,
  80. index = itemIndex,
  81. item = newItem
  82. };
  83. changes.Add(change);
  84. Callback?.Invoke(op, itemIndex, oldItem, newItem);
  85. }
  86. public void OnSerializeAll(NetworkWriter writer)
  87. {
  88. // if init, write the full list content
  89. writer.WriteUInt((uint)objects.Count);
  90. for (int i = 0; i < objects.Count; i++)
  91. {
  92. T obj = objects[i];
  93. writer.Write(obj);
  94. }
  95. // all changes have been applied already
  96. // thus the client will need to skip all the pending changes
  97. // or they would be applied again.
  98. // So we write how many changes are pending
  99. writer.WriteUInt((uint)changes.Count);
  100. }
  101. public void OnSerializeDelta(NetworkWriter writer)
  102. {
  103. // write all the queued up changes
  104. writer.WriteUInt((uint)changes.Count);
  105. for (int i = 0; i < changes.Count; i++)
  106. {
  107. Change change = changes[i];
  108. writer.WriteByte((byte)change.operation);
  109. switch (change.operation)
  110. {
  111. case Operation.OP_ADD:
  112. writer.Write(change.item);
  113. break;
  114. case Operation.OP_CLEAR:
  115. break;
  116. case Operation.OP_REMOVEAT:
  117. writer.WriteUInt((uint)change.index);
  118. break;
  119. case Operation.OP_INSERT:
  120. case Operation.OP_SET:
  121. writer.WriteUInt((uint)change.index);
  122. writer.Write(change.item);
  123. break;
  124. }
  125. }
  126. }
  127. public void OnDeserializeAll(NetworkReader reader)
  128. {
  129. // This list can now only be modified by synchronization
  130. IsReadOnly = true;
  131. // if init, write the full list content
  132. int count = (int)reader.ReadUInt();
  133. objects.Clear();
  134. changes.Clear();
  135. for (int i = 0; i < count; i++)
  136. {
  137. T obj = reader.Read<T>();
  138. objects.Add(obj);
  139. }
  140. // We will need to skip all these changes
  141. // the next time the list is synchronized
  142. // because they have already been applied
  143. changesAhead = (int)reader.ReadUInt();
  144. }
  145. public void OnDeserializeDelta(NetworkReader reader)
  146. {
  147. // This list can now only be modified by synchronization
  148. IsReadOnly = true;
  149. int changesCount = (int)reader.ReadUInt();
  150. for (int i = 0; i < changesCount; i++)
  151. {
  152. Operation operation = (Operation)reader.ReadByte();
  153. // apply the operation only if it is a new change
  154. // that we have not applied yet
  155. bool apply = changesAhead == 0;
  156. int index = 0;
  157. T oldItem = default;
  158. T newItem = default;
  159. switch (operation)
  160. {
  161. case Operation.OP_ADD:
  162. newItem = reader.Read<T>();
  163. if (apply)
  164. {
  165. index = objects.Count;
  166. objects.Add(newItem);
  167. }
  168. break;
  169. case Operation.OP_CLEAR:
  170. if (apply)
  171. {
  172. objects.Clear();
  173. }
  174. break;
  175. case Operation.OP_INSERT:
  176. index = (int)reader.ReadUInt();
  177. newItem = reader.Read<T>();
  178. if (apply)
  179. {
  180. objects.Insert(index, newItem);
  181. }
  182. break;
  183. case Operation.OP_REMOVEAT:
  184. index = (int)reader.ReadUInt();
  185. if (apply)
  186. {
  187. oldItem = objects[index];
  188. objects.RemoveAt(index);
  189. }
  190. break;
  191. case Operation.OP_SET:
  192. index = (int)reader.ReadUInt();
  193. newItem = reader.Read<T>();
  194. if (apply)
  195. {
  196. oldItem = objects[index];
  197. objects[index] = newItem;
  198. }
  199. break;
  200. }
  201. if (apply)
  202. {
  203. Callback?.Invoke(operation, index, oldItem, newItem);
  204. }
  205. // we just skipped this change
  206. else
  207. {
  208. changesAhead--;
  209. }
  210. }
  211. }
  212. public void Add(T item)
  213. {
  214. objects.Add(item);
  215. AddOperation(Operation.OP_ADD, objects.Count - 1, default, item);
  216. }
  217. public void AddRange(IEnumerable<T> range)
  218. {
  219. foreach (T entry in range)
  220. {
  221. Add(entry);
  222. }
  223. }
  224. public void Clear()
  225. {
  226. objects.Clear();
  227. AddOperation(Operation.OP_CLEAR, 0, default, default);
  228. }
  229. public bool Contains(T item) => IndexOf(item) >= 0;
  230. public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);
  231. public int IndexOf(T item)
  232. {
  233. for (int i = 0; i < objects.Count; ++i)
  234. if (comparer.Equals(item, objects[i]))
  235. return i;
  236. return -1;
  237. }
  238. public int FindIndex(Predicate<T> match)
  239. {
  240. for (int i = 0; i < objects.Count; ++i)
  241. if (match(objects[i]))
  242. return i;
  243. return -1;
  244. }
  245. public T Find(Predicate<T> match)
  246. {
  247. int i = FindIndex(match);
  248. return (i != -1) ? objects[i] : default;
  249. }
  250. public List<T> FindAll(Predicate<T> match)
  251. {
  252. List<T> results = new List<T>();
  253. for (int i = 0; i < objects.Count; ++i)
  254. if (match(objects[i]))
  255. results.Add(objects[i]);
  256. return results;
  257. }
  258. public void Insert(int index, T item)
  259. {
  260. objects.Insert(index, item);
  261. AddOperation(Operation.OP_INSERT, index, default, item);
  262. }
  263. public void InsertRange(int index, IEnumerable<T> range)
  264. {
  265. foreach (T entry in range)
  266. {
  267. Insert(index, entry);
  268. index++;
  269. }
  270. }
  271. public bool Remove(T item)
  272. {
  273. int index = IndexOf(item);
  274. bool result = index >= 0;
  275. if (result)
  276. {
  277. RemoveAt(index);
  278. }
  279. return result;
  280. }
  281. public void RemoveAt(int index)
  282. {
  283. T oldItem = objects[index];
  284. objects.RemoveAt(index);
  285. AddOperation(Operation.OP_REMOVEAT, index, oldItem, default);
  286. }
  287. public int RemoveAll(Predicate<T> match)
  288. {
  289. List<T> toRemove = new List<T>();
  290. for (int i = 0; i < objects.Count; ++i)
  291. if (match(objects[i]))
  292. toRemove.Add(objects[i]);
  293. foreach (T entry in toRemove)
  294. {
  295. Remove(entry);
  296. }
  297. return toRemove.Count;
  298. }
  299. public T this[int i]
  300. {
  301. get => objects[i];
  302. set
  303. {
  304. if (!comparer.Equals(objects[i], value))
  305. {
  306. T oldItem = objects[i];
  307. objects[i] = value;
  308. AddOperation(Operation.OP_SET, i, oldItem, value);
  309. }
  310. }
  311. }
  312. public Enumerator GetEnumerator() => new Enumerator(this);
  313. IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
  314. IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
  315. // default Enumerator allocates. we need a custom struct Enumerator to
  316. // not allocate on the heap.
  317. // (System.Collections.Generic.List<T> source code does the same)
  318. //
  319. // benchmark:
  320. // uMMORPG with 800 monsters, Skills.GetHealthBonus() which runs a
  321. // foreach on skills SyncList:
  322. // before: 81.2KB GC per frame
  323. // after: 0KB GC per frame
  324. // => this is extremely important for MMO scale networking
  325. public struct Enumerator : IEnumerator<T>
  326. {
  327. readonly SyncList<T> list;
  328. int index;
  329. public T Current { get; private set; }
  330. public Enumerator(SyncList<T> list)
  331. {
  332. this.list = list;
  333. index = -1;
  334. Current = default;
  335. }
  336. public bool MoveNext()
  337. {
  338. if (++index >= list.Count)
  339. {
  340. return false;
  341. }
  342. Current = list[index];
  343. return true;
  344. }
  345. public void Reset() => index = -1;
  346. object IEnumerator.Current => Current;
  347. public void Dispose() {}
  348. }
  349. }
  350. }