diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2018-12-07 13:40:44 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2018-12-07 13:40:44 -0500 |
commit | a78b1935928919694dfe8de823a1accd6d222732 (patch) | |
tree | 3f17b6087cf2749e52c1e237de17e2e9addb6c06 /src/SMAPI/Framework/StateTracking | |
parent | 4cd9eda1591c3908bf80b60c2902491a7595ee27 (diff) | |
parent | 8901218418693d610a17b22fe789ba6279f63446 (diff) | |
download | SMAPI-a78b1935928919694dfe8de823a1accd6d222732.tar.gz SMAPI-a78b1935928919694dfe8de823a1accd6d222732.tar.bz2 SMAPI-a78b1935928919694dfe8de823a1accd6d222732.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework/StateTracking')
8 files changed, 146 insertions, 30 deletions
diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs new file mode 100644 index 00000000..2ea6609a --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// <summary>A watcher which detects changes to a collection of values using a specified <see cref="IEqualityComparer{T}"/> instance.</summary> + /// <typeparam name="TValue">The value type within the collection.</typeparam> + internal class ComparableListWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue> + { + /********* + ** Properties + *********/ + /// <summary>The collection to watch.</summary> + private readonly ICollection<TValue> CurrentValues; + + /// <summary>The values during the previous update.</summary> + private HashSet<TValue> LastValues; + + /// <summary>The pairs added since the last reset.</summary> + private readonly List<TValue> AddedImpl = new List<TValue>(); + + /// <summary>The pairs removed since the last reset.</summary> + private readonly List<TValue> RemovedImpl = new List<TValue>(); + + + /********* + ** Accessors + *********/ + /// <summary>Whether the value changed since the last reset.</summary> + public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0; + + /// <summary>The values added since the last reset.</summary> + public IEnumerable<TValue> Added => this.AddedImpl; + + /// <summary>The values removed since the last reset.</summary> + public IEnumerable<TValue> Removed => this.RemovedImpl; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="values">The collection to watch.</param> + /// <param name="comparer">The equality comparer which indicates whether two values are the same.</param> + public ComparableListWatcher(ICollection<TValue> values, IEqualityComparer<TValue> comparer) + { + this.CurrentValues = values; + this.LastValues = new HashSet<TValue>(comparer); + } + + /// <summary>Update the current value if needed.</summary> + public void Update() + { + this.AssertNotDisposed(); + + // optimise for zero items + if (this.CurrentValues.Count == 0) + { + if (this.LastValues.Count > 0) + { + this.AddedImpl.AddRange(this.LastValues); + this.LastValues.Clear(); + } + return; + } + + // detect changes + HashSet<TValue> curValues = new HashSet<TValue>(this.CurrentValues, this.LastValues.Comparer); + this.RemovedImpl.AddRange(from value in this.LastValues where !curValues.Contains(value) select value); + this.AddedImpl.AddRange(from value in curValues where !this.LastValues.Contains(value) select value); + this.LastValues = curValues; + } + + /// <summary>Set the current value as the baseline.</summary> + public void Reset() + { + this.AssertNotDisposed(); + + this.AddedImpl.Clear(); + this.RemovedImpl.Clear(); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs index d51fc2ac..dda30a15 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs @@ -4,26 +4,27 @@ using System.Collections.Generic; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { /// <summary>A watcher which detects changes to a value using a specified <see cref="IEqualityComparer{T}"/> instance.</summary> - internal class ComparableWatcher<T> : IValueWatcher<T> + /// <typeparam name="TValue">The comparable value type.</typeparam> + internal class ComparableWatcher<TValue> : IValueWatcher<TValue> { /********* ** Properties *********/ /// <summary>Get the current value.</summary> - private readonly Func<T> GetValue; + private readonly Func<TValue> GetValue; /// <summary>The equality comparer.</summary> - private readonly IEqualityComparer<T> Comparer; + private readonly IEqualityComparer<TValue> Comparer; /********* ** Accessors *********/ /// <summary>The field value at the last reset.</summary> - public T PreviousValue { get; private set; } + public TValue PreviousValue { get; private set; } /// <summary>The latest value.</summary> - public T CurrentValue { get; private set; } + public TValue CurrentValue { get; private set; } /// <summary>Whether the value changed since the last reset.</summary> public bool IsChanged { get; private set; } @@ -35,7 +36,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>Construct an instance.</summary> /// <param name="getValue">Get the current value.</param> /// <param name="comparer">The equality comparer which indicates whether two values are the same.</param> - public ComparableWatcher(Func<T> getValue, IEqualityComparer<T> comparer) + public ComparableWatcher(Func<TValue> getValue, IEqualityComparer<TValue> comparer) { this.GetValue = getValue; this.Comparer = comparer; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs index 8a841a79..d3022a69 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs @@ -4,6 +4,7 @@ using Netcode; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { /// <summary>A watcher which detects changes to a Netcode collection.</summary> + /// <typeparam name="TValue">The value type within the collection.</typeparam> internal class NetCollectionWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue> where TValue : class, INetObject<INetSerializable> { @@ -16,7 +17,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>The pairs added since the last reset.</summary> private readonly List<TValue> AddedImpl = new List<TValue>(); - /// <summary>The pairs demoved since the last reset.</summary> + /// <summary>The pairs removed since the last reset.</summary> private readonly List<TValue> RemovedImpl = new List<TValue>(); diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs index 7a2bf84e..7a7ab89d 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs @@ -20,7 +20,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>The pairs added since the last reset.</summary> private readonly IDictionary<TKey, TValue> PairsAdded = new Dictionary<TKey, TValue>(); - /// <summary>The pairs demoved since the last reset.</summary> + /// <summary>The pairs removed since the last reset.</summary> private readonly IDictionary<TKey, TValue> PairsRemoved = new Dictionary<TKey, TValue>(); /// <summary>The field being watched.</summary> diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs index 188ed9f3..85099988 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs @@ -3,13 +3,15 @@ using Netcode; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { /// <summary>A watcher which detects changes to a net value field.</summary> - internal class NetValueWatcher<T, TSelf> : BaseDisposableWatcher, IValueWatcher<T> where TSelf : NetFieldBase<T, TSelf> + /// <typeparam name="TValue">The value type wrapped by the net field.</typeparam> + /// <typeparam name="TNetField">The net field type.</typeparam> + internal class NetValueWatcher<TValue, TNetField> : BaseDisposableWatcher, IValueWatcher<TValue> where TNetField : NetFieldBase<TValue, TNetField> { /********* ** Properties *********/ /// <summary>The field being watched.</summary> - private readonly NetFieldBase<T, TSelf> Field; + private readonly NetFieldBase<TValue, TNetField> Field; /********* @@ -19,10 +21,10 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers public bool IsChanged { get; private set; } /// <summary>The field value at the last reset.</summary> - public T PreviousValue { get; private set; } + public TValue PreviousValue { get; private set; } /// <summary>The latest value.</summary> - public T CurrentValue { get; private set; } + public TValue CurrentValue { get; private set; } /********* @@ -30,7 +32,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers *********/ /// <summary>Construct an instance.</summary> /// <param name="field">The field to watch.</param> - public NetValueWatcher(NetFieldBase<T, TSelf> field) + public NetValueWatcher(NetFieldBase<TValue, TNetField> field) { this.Field = field; this.PreviousValue = field.Value; @@ -74,7 +76,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <param name="field">The field being watched.</param> /// <param name="oldValue">The old field value.</param> /// <param name="newValue">The new field value.</param> - private void OnValueChanged(TSelf field, T oldValue, T newValue) + private void OnValueChanged(TNetField field, TValue oldValue, TValue newValue) { this.CurrentValue = newValue; this.IsChanged = true; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs index 34a97097..0c65789f 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -6,6 +6,7 @@ using System.Linq; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { /// <summary>A watcher which detects changes to an observable collection.</summary> + /// <typeparam name="TValue">The value type within the collection.</typeparam> internal class ObservableCollectionWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue> { /********* @@ -17,7 +18,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>The pairs added since the last reset.</summary> private readonly List<TValue> AddedImpl = new List<TValue>(); - /// <summary>The pairs demoved since the last reset.</summary> + /// <summary>The pairs removed since the last reset.</summary> private readonly List<TValue> RemovedImpl = new List<TValue>(); diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index ab4ab0d5..8301351e 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -36,6 +36,14 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers return new ComparableWatcher<T>(getValue, new ObjectReferenceComparer<T>()); } + /// <summary>Get a watcher which detects when an object reference in a collection changes.</summary> + /// <typeparam name="T">The value type.</typeparam> + /// <param name="collection">The observable collection.</param> + public static ComparableListWatcher<T> ForReferenceList<T>(ICollection<T> collection) + { + return new ComparableListWatcher<T>(collection, new ObjectReferenceComparer<T>()); + } + /// <summary>Get a watcher for an observable collection.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="collection">The observable collection.</param> diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index 5a259663..d9d598f8 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -19,6 +18,9 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>Tracks changes to the location list.</summary> private readonly ICollectionWatcher<GameLocation> LocationListWatcher; + /// <summary>Tracks changes to the list of active mine locations.</summary> + private readonly ICollectionWatcher<MineShaft> MineLocationListWatcher; + /// <summary>A lookup of the tracked locations.</summary> private IDictionary<GameLocation, LocationTracker> LocationDict { get; } = new Dictionary<GameLocation, LocationTracker>(new ObjectReferenceComparer<GameLocation>()); @@ -50,24 +52,34 @@ namespace StardewModdingAPI.Framework.StateTracking *********/ /// <summary>Construct an instance.</summary> /// <param name="locations">The game's list of locations.</param> - public WorldLocationsTracker(ObservableCollection<GameLocation> locations) + /// <param name="activeMineLocations">The game's list of active mine locations.</param> + public WorldLocationsTracker(ObservableCollection<GameLocation> locations, IList<MineShaft> activeMineLocations) { this.LocationListWatcher = WatcherFactory.ForObservableCollection(locations); + this.MineLocationListWatcher = WatcherFactory.ForReferenceList(activeMineLocations); } /// <summary>Update the current value if needed.</summary> public void Update() { - // detect location changes + // detect added/removed locations + this.LocationListWatcher.Update(); + this.MineLocationListWatcher.Update(); if (this.LocationListWatcher.IsChanged) { this.Remove(this.LocationListWatcher.Removed); this.Add(this.LocationListWatcher.Added); } + if (this.MineLocationListWatcher.IsChanged) + { + this.Remove(this.MineLocationListWatcher.Removed); + this.Add(this.MineLocationListWatcher.Added); + } - // detect building changes + // detect building changed foreach (LocationTracker watcher in this.Locations.ToArray()) { + watcher.Update(); if (watcher.BuildingsWatcher.IsChanged) { this.Remove(watcher.BuildingsWatcher.Removed); @@ -75,7 +87,7 @@ namespace StardewModdingAPI.Framework.StateTracking } } - // detect building interior changed (e.g. construction completed) + // detect building interiors changed (e.g. construction completed) foreach (KeyValuePair<Building, GameLocation> pair in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value))) { GameLocation oldIndoors = pair.Value; @@ -86,10 +98,6 @@ namespace StardewModdingAPI.Framework.StateTracking if (newIndoors != null) this.Removed.Add(newIndoors); } - - // update watchers - foreach (IWatcher watcher in this.Locations) - watcher.Update(); } /// <summary>Set the current location list as the baseline.</summary> @@ -98,21 +106,21 @@ namespace StardewModdingAPI.Framework.StateTracking this.Removed.Clear(); this.Added.Clear(); this.LocationListWatcher.Reset(); + this.MineLocationListWatcher.Reset(); } /// <summary>Set the current value as the baseline.</summary> public void Reset() { this.ResetLocationList(); - foreach (IWatcher watcher in this.Locations) + foreach (IWatcher watcher in this.GetWatchers()) watcher.Reset(); } /// <summary>Stop watching the player fields and release all references.</summary> public void Dispose() { - this.LocationListWatcher.Dispose(); - foreach (IWatcher watcher in this.Locations) + foreach (IWatcher watcher in this.GetWatchers()) watcher.Dispose(); } @@ -180,11 +188,11 @@ namespace StardewModdingAPI.Framework.StateTracking // remove old location if needed this.Remove(location); - // track change + // add location this.Added.Add(location); - - // add this.LocationDict[location] = new LocationTracker(location); + + // add buildings if (location is BuildableGameLocation buildableLocation) this.Add(buildableLocation.buildings); } @@ -219,5 +227,17 @@ namespace StardewModdingAPI.Framework.StateTracking this.Remove(buildableLocation.buildings); } } + + /**** + ** Helpers + ****/ + /// <summary>The underlying watchers.</summary> + private IEnumerable<IWatcher> GetWatchers() + { + yield return this.LocationListWatcher; + yield return this.MineLocationListWatcher; + foreach (LocationTracker watcher in this.Locations) + yield return watcher; + } } } |