SyncList.cs 12 KB

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