using System.Collections.Generic; using Netcode; using StardewModdingAPI.Framework.StateTracking.Comparers; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { /// A watcher which detects changes to a net list field. /// The list value type. internal class NetListWatcher : BaseDisposableWatcher, ICollectionWatcher where TValue : class, INetObject { /********* ** Fields *********/ /// The field being watched. private readonly NetList> Field; /// The pairs added since the last reset. private readonly ISet AddedImpl = new HashSet(new ObjectReferenceComparer()); /// The pairs removed since the last reset. private readonly ISet RemovedImpl = new HashSet(new ObjectReferenceComparer()); /********* ** Accessors *********/ /// public string Name { get; } /// public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0; /// public IEnumerable Added => this.AddedImpl; /// public IEnumerable Removed => this.RemovedImpl; /********* ** Public methods *********/ /// Construct an instance. /// A name which identifies what the watcher is watching, used for troubleshooting. /// The field to watch. public NetListWatcher(string name, NetList> field) { this.Name = name; this.Field = field; field.OnElementChanged += this.OnElementChanged; field.OnArrayReplaced += this.OnArrayReplaced; } /// public void Reset() { this.AddedImpl.Clear(); this.RemovedImpl.Clear(); } /// public void Update() { this.AssertNotDisposed(); } /// public override void Dispose() { if (!this.IsDisposed) { this.Field.OnElementChanged -= this.OnElementChanged; this.Field.OnArrayReplaced -= this.OnArrayReplaced; } base.Dispose(); } /********* ** Private methods *********/ /// A callback invoked when the value list is replaced. /// The net field whose values changed. /// The previous list of values. /// The new list of values. private void OnArrayReplaced(NetList> list, IList oldValues, IList newValues) { ISet oldSet = new HashSet(oldValues, new ObjectReferenceComparer()); ISet changed = new HashSet(newValues, new ObjectReferenceComparer()); foreach (TValue value in oldSet) { if (!changed.Contains(value)) this.Remove(value); } foreach (TValue value in changed) { if (!oldSet.Contains(value)) this.Add(value); } } /// A callback invoked when an entry is replaced. /// The net field whose values changed. /// The list index which changed. /// The previous value. /// The new value. private void OnElementChanged(NetList> list, int index, TValue? oldValue, TValue? newValue) { this.Remove(oldValue); this.Add(newValue); } /// Track an added item. /// The value that was added. private void Add(TValue? value) { if (value == null) return; if (this.RemovedImpl.Contains(value)) { this.AddedImpl.Remove(value); this.RemovedImpl.Remove(value); } else this.AddedImpl.Add(value); } /// Track a removed item. /// The value that was removed. private void Remove(TValue? value) { if (value == null) return; if (this.AddedImpl.Contains(value)) { this.AddedImpl.Remove(value); this.RemovedImpl.Remove(value); } else this.RemovedImpl.Add(value); } } }