using System; using System.Collections.Generic; using Unity.Collections; namespace Unity.Netcode { /// /// Event based NetworkVariable container for syncing Lists /// /// The type for the list public class NetworkList : NetworkVariableSerialization where T : unmanaged, IEquatable { private NativeList m_List = new NativeList(64, Allocator.Persistent); private NativeList m_ListAtLastReset = new NativeList(64, Allocator.Persistent); private NativeList> m_DirtyEvents = new NativeList>(64, Allocator.Persistent); /// /// Delegate type for list changed event /// /// Struct containing information about the change event public delegate void OnListChangedDelegate(NetworkListEvent changeEvent); /// /// The callback to be invoked when the list gets changed /// public event OnListChangedDelegate OnListChanged; public NetworkList() { } public NetworkList(IEnumerable values = default, NetworkVariableReadPermission readPerm = DefaultReadPerm, NetworkVariableWritePermission writePerm = DefaultWritePerm) : base(readPerm, writePerm) { foreach (var value in values) { m_List.Add(value); } } /// public override void ResetDirty() { base.ResetDirty(); if (m_DirtyEvents.Length > 0) { m_DirtyEvents.Clear(); m_ListAtLastReset.CopyFrom(m_List); } } /// public override bool IsDirty() { // we call the base class to allow the SetDirty() mechanism to work return base.IsDirty() || m_DirtyEvents.Length > 0; } /// public override void WriteDelta(FastBufferWriter writer) { if (base.IsDirty()) { writer.WriteValueSafe((ushort)1); writer.WriteValueSafe(NetworkListEvent.EventType.Full); WriteField(writer); return; } writer.WriteValueSafe((ushort)m_DirtyEvents.Length); for (int i = 0; i < m_DirtyEvents.Length; i++) { writer.WriteValueSafe(m_DirtyEvents[i].Type); switch (m_DirtyEvents[i].Type) { case NetworkListEvent.EventType.Add: { Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Insert: { writer.WriteValueSafe(m_DirtyEvents[i].Index); Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Remove: { Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.RemoveAt: { writer.WriteValueSafe(m_DirtyEvents[i].Index); } break; case NetworkListEvent.EventType.Value: { writer.WriteValueSafe(m_DirtyEvents[i].Index); Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Clear: { //Nothing has to be written } break; } } } /// public override void WriteField(FastBufferWriter writer) { writer.WriteValueSafe((ushort)m_ListAtLastReset.Length); for (int i = 0; i < m_ListAtLastReset.Length; i++) { Write(writer, m_ListAtLastReset[i]); } } /// public override void ReadField(FastBufferReader reader) { m_List.Clear(); reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { Read(reader, out T value); m_List.Add(value); } } /// public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { reader.ReadValueSafe(out ushort deltaCount); for (int i = 0; i < deltaCount; i++) { reader.ReadValueSafe(out NetworkListEvent.EventType eventType); switch (eventType) { case NetworkListEvent.EventType.Add: { Read(reader, out T value); m_List.Add(value); if (OnListChanged != null) { OnListChanged(new NetworkListEvent { Type = eventType, Index = m_List.Length - 1, Value = m_List[m_List.Length - 1] }); } if (keepDirtyDelta) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType, Index = m_List.Length - 1, Value = m_List[m_List.Length - 1] }); } } break; case NetworkListEvent.EventType.Insert: { reader.ReadValueSafe(out int index); Read(reader, out T value); m_List.InsertRangeWithBeginEnd(index, index + 1); m_List[index] = value; if (OnListChanged != null) { OnListChanged(new NetworkListEvent { Type = eventType, Index = index, Value = m_List[index] }); } if (keepDirtyDelta) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType, Index = index, Value = m_List[index] }); } } break; case NetworkListEvent.EventType.Remove: { Read(reader, out T value); int index = m_List.IndexOf(value); if (index == -1) { break; } m_List.RemoveAt(index); if (OnListChanged != null) { OnListChanged(new NetworkListEvent { Type = eventType, Index = index, Value = value }); } if (keepDirtyDelta) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType, Index = index, Value = value }); } } break; case NetworkListEvent.EventType.RemoveAt: { reader.ReadValueSafe(out int index); T value = m_List[index]; m_List.RemoveAt(index); if (OnListChanged != null) { OnListChanged(new NetworkListEvent { Type = eventType, Index = index, Value = value }); } if (keepDirtyDelta) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType, Index = index, Value = value }); } } break; case NetworkListEvent.EventType.Value: { reader.ReadValueSafe(out int index); Read(reader, out T value); if (index >= m_List.Length) { throw new Exception("Shouldn't be here, index is higher than list length"); } var previousValue = m_List[index]; m_List[index] = value; if (OnListChanged != null) { OnListChanged(new NetworkListEvent { Type = eventType, Index = index, Value = value, PreviousValue = previousValue }); } if (keepDirtyDelta) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType, Index = index, Value = value, PreviousValue = previousValue }); } } break; case NetworkListEvent.EventType.Clear: { //Read nothing m_List.Clear(); if (OnListChanged != null) { OnListChanged(new NetworkListEvent { Type = eventType, }); } if (keepDirtyDelta) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType }); } } break; case NetworkListEvent.EventType.Full: { ReadField(reader); ResetDirty(); } break; } } } /// public IEnumerator GetEnumerator() { return m_List.GetEnumerator(); } /// public void Add(T item) { m_List.Add(item); var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.Add, Value = item, Index = m_List.Length - 1 }; HandleAddListEvent(listEvent); } /// public void Clear() { m_List.Clear(); var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.Clear }; HandleAddListEvent(listEvent); } /// public bool Contains(T item) { int index = NativeArrayExtensions.IndexOf(m_List, item); return index != -1; } /// public bool Remove(T item) { int index = NativeArrayExtensions.IndexOf(m_List, item); if (index == -1) { return false; } m_List.RemoveAt(index); var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.Remove, Value = item }; HandleAddListEvent(listEvent); return true; } /// public int Count => m_List.Length; /// public int IndexOf(T item) { return m_List.IndexOf(item); } /// public void Insert(int index, T item) { m_List.InsertRangeWithBeginEnd(index, index + 1); m_List[index] = item; var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.Insert, Index = index, Value = item }; HandleAddListEvent(listEvent); } /// public void RemoveAt(int index) { m_List.RemoveAt(index); var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.RemoveAt, Index = index }; HandleAddListEvent(listEvent); } /// public T this[int index] { get => m_List[index]; set { m_List[index] = value; var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.Value, Index = index, Value = value }; HandleAddListEvent(listEvent); } } private void HandleAddListEvent(NetworkListEvent listEvent) { m_DirtyEvents.Add(listEvent); OnListChanged?.Invoke(listEvent); } public int LastModifiedTick { get { // todo: implement proper network tick for NetworkList return NetworkTickSystem.NoTick; } } public override void Dispose() { m_List.Dispose(); m_ListAtLastReset.Dispose(); m_DirtyEvents.Dispose(); } } /// /// Struct containing event information about changes to a NetworkList. /// /// The type for the list that the event is about public struct NetworkListEvent { /// /// Enum representing the different operations available for triggering an event. /// public enum EventType : byte { /// /// Add /// Add, /// /// Insert /// Insert, /// /// Remove /// Remove, /// /// Remove at /// RemoveAt, /// /// Value changed /// Value, /// /// Clear /// Clear, /// /// Full list refresh /// Full } /// /// Enum representing the operation made to the list. /// public EventType Type; /// /// The value changed, added or removed if available. /// public T Value; /// /// The previous value when "Value" has changed, if available. /// public T PreviousValue; /// /// the index changed, added or removed if available /// public int Index; } }