| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 | using System;using System.Collections;using System.Collections.Generic;namespace Mirror{    // Deprecated 2020-10-02    [Obsolete("Use SyncList<string> instead")]    public class SyncListString : SyncList<string> {}    // Deprecated 2020-10-02    [Obsolete("Use SyncList<float> instead")]    public class SyncListFloat : SyncList<float> {}    // Deprecated 2020-10-02    [Obsolete("Use SyncList<int> instead")]    public class SyncListInt : SyncList<int> {}    // Deprecated 2020-10-02    [Obsolete("Use SyncList<uint> instead")]    public class SyncListUInt : SyncList<uint> {}    // Deprecated 2020-10-02    [Obsolete("Use SyncList<bool> instead")]    public class SyncListBool : SyncList<bool> {}    public class SyncList<T> : IList<T>, IReadOnlyList<T>, SyncObject    {        public delegate void SyncListChanged(Operation op, int itemIndex, T oldItem, T newItem);        readonly IList<T> objects;        readonly IEqualityComparer<T> comparer;        public int Count => objects.Count;        public bool IsReadOnly { get; private set; }        public event SyncListChanged Callback;        public enum Operation : byte        {            OP_ADD,            OP_CLEAR,            OP_INSERT,            OP_REMOVEAT,            OP_SET        }        struct Change        {            internal Operation operation;            internal int index;            internal T item;        }        readonly List<Change> changes = new List<Change>();        // how many changes we need to ignore        // this is needed because when we initialize the list,        // we might later receive changes that have already been applied        // so we need to skip them        int changesAhead;        public SyncList() : this(EqualityComparer<T>.Default) {}        public SyncList(IEqualityComparer<T> comparer)        {            this.comparer = comparer ?? EqualityComparer<T>.Default;            objects = new List<T>();        }        public SyncList(IList<T> objects, IEqualityComparer<T> comparer = null)        {            this.comparer = comparer ?? EqualityComparer<T>.Default;            this.objects = objects;        }        public bool IsDirty => changes.Count > 0;        // throw away all the changes        // this should be called after a successful sync        public void Flush() => changes.Clear();        public void Reset()        {            IsReadOnly = false;            changes.Clear();            changesAhead = 0;            objects.Clear();        }        void AddOperation(Operation op, int itemIndex, T oldItem, T newItem)        {            if (IsReadOnly)            {                throw new InvalidOperationException("Synclists can only be modified at the server");            }            Change change = new Change            {                operation = op,                index = itemIndex,                item = newItem            };            changes.Add(change);            Callback?.Invoke(op, itemIndex, oldItem, newItem);        }        public void OnSerializeAll(NetworkWriter writer)        {            // if init,  write the full list content            writer.WriteUInt((uint)objects.Count);            for (int i = 0; i < objects.Count; i++)            {                T obj = objects[i];                writer.Write(obj);            }            // all changes have been applied already            // thus the client will need to skip all the pending changes            // or they would be applied again.            // So we write how many changes are pending            writer.WriteUInt((uint)changes.Count);        }        public void OnSerializeDelta(NetworkWriter writer)        {            // write all the queued up changes            writer.WriteUInt((uint)changes.Count);            for (int i = 0; i < changes.Count; i++)            {                Change change = changes[i];                writer.WriteByte((byte)change.operation);                switch (change.operation)                {                    case Operation.OP_ADD:                        writer.Write(change.item);                        break;                    case Operation.OP_CLEAR:                        break;                    case Operation.OP_REMOVEAT:                        writer.WriteUInt((uint)change.index);                        break;                    case Operation.OP_INSERT:                    case Operation.OP_SET:                        writer.WriteUInt((uint)change.index);                        writer.Write(change.item);                        break;                }            }        }        public void OnDeserializeAll(NetworkReader reader)        {            // This list can now only be modified by synchronization            IsReadOnly = true;            // if init,  write the full list content            int count = (int)reader.ReadUInt();            objects.Clear();            changes.Clear();            for (int i = 0; i < count; i++)            {                T obj = reader.Read<T>();                objects.Add(obj);            }            // We will need to skip all these changes            // the next time the list is synchronized            // because they have already been applied            changesAhead = (int)reader.ReadUInt();        }        public void OnDeserializeDelta(NetworkReader reader)        {            // This list can now only be modified by synchronization            IsReadOnly = true;            int changesCount = (int)reader.ReadUInt();            for (int i = 0; i < changesCount; i++)            {                Operation operation = (Operation)reader.ReadByte();                // apply the operation only if it is a new change                // that we have not applied yet                bool apply = changesAhead == 0;                int index = 0;                T oldItem = default;                T newItem = default;                switch (operation)                {                    case Operation.OP_ADD:                        newItem = reader.Read<T>();                        if (apply)                        {                            index = objects.Count;                            objects.Add(newItem);                        }                        break;                    case Operation.OP_CLEAR:                        if (apply)                        {                            objects.Clear();                        }                        break;                    case Operation.OP_INSERT:                        index = (int)reader.ReadUInt();                        newItem = reader.Read<T>();                        if (apply)                        {                            objects.Insert(index, newItem);                        }                        break;                    case Operation.OP_REMOVEAT:                        index = (int)reader.ReadUInt();                        if (apply)                        {                            oldItem = objects[index];                            objects.RemoveAt(index);                        }                        break;                    case Operation.OP_SET:                        index = (int)reader.ReadUInt();                        newItem = reader.Read<T>();                        if (apply)                        {                            oldItem = objects[index];                            objects[index] = newItem;                        }                        break;                }                if (apply)                {                    Callback?.Invoke(operation, index, oldItem, newItem);                }                // we just skipped this change                else                {                    changesAhead--;                }            }        }        public void Add(T item)        {            objects.Add(item);            AddOperation(Operation.OP_ADD, objects.Count - 1, default, item);        }        public void AddRange(IEnumerable<T> range)        {            foreach (T entry in range)            {                Add(entry);            }        }        public void Clear()        {            objects.Clear();            AddOperation(Operation.OP_CLEAR, 0, default, default);        }        public bool Contains(T item) => IndexOf(item) >= 0;        public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);        public int IndexOf(T item)        {            for (int i = 0; i < objects.Count; ++i)                if (comparer.Equals(item, objects[i]))                    return i;            return -1;        }        public int FindIndex(Predicate<T> match)        {            for (int i = 0; i < objects.Count; ++i)                if (match(objects[i]))                    return i;            return -1;        }        public T Find(Predicate<T> match)        {            int i = FindIndex(match);            return (i != -1) ? objects[i] : default;        }        public List<T> FindAll(Predicate<T> match)        {            List<T> results = new List<T>();            for (int i = 0; i < objects.Count; ++i)                if (match(objects[i]))                    results.Add(objects[i]);            return results;        }        public void Insert(int index, T item)        {            objects.Insert(index, item);            AddOperation(Operation.OP_INSERT, index, default, item);        }        public void InsertRange(int index, IEnumerable<T> range)        {            foreach (T entry in range)            {                Insert(index, entry);                index++;            }        }        public bool Remove(T item)        {            int index = IndexOf(item);            bool result = index >= 0;            if (result)            {                RemoveAt(index);            }            return result;        }        public void RemoveAt(int index)        {            T oldItem = objects[index];            objects.RemoveAt(index);            AddOperation(Operation.OP_REMOVEAT, index, oldItem, default);        }        public int RemoveAll(Predicate<T> match)        {            List<T> toRemove = new List<T>();            for (int i = 0; i < objects.Count; ++i)                if (match(objects[i]))                    toRemove.Add(objects[i]);            foreach (T entry in toRemove)            {                Remove(entry);            }            return toRemove.Count;        }        public T this[int i]        {            get => objects[i];            set            {                if (!comparer.Equals(objects[i], value))                {                    T oldItem = objects[i];                    objects[i] = value;                    AddOperation(Operation.OP_SET, i, oldItem, value);                }            }        }        public Enumerator GetEnumerator() => new Enumerator(this);        IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);        IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);        // default Enumerator allocates. we need a custom struct Enumerator to        // not allocate on the heap.        // (System.Collections.Generic.List<T> source code does the same)        //        // benchmark:        //   uMMORPG with 800 monsters, Skills.GetHealthBonus() which runs a        //   foreach on skills SyncList:        //      before: 81.2KB GC per frame        //      after:     0KB GC per frame        // => this is extremely important for MMO scale networking        public struct Enumerator : IEnumerator<T>        {            readonly SyncList<T> list;            int index;            public T Current { get; private set; }            public Enumerator(SyncList<T> list)            {                this.list = list;                index = -1;                Current = default;            }            public bool MoveNext()            {                if (++index >= list.Count)                {                    return false;                }                Current = list[index];                return true;            }            public void Reset() => index = -1;            object IEnumerator.Current => Current;            public void Dispose() {}        }    }}
 |