diff options
Diffstat (limited to 'src/SMAPI/Framework/StateTracking')
4 files changed, 79 insertions, 29 deletions
diff --git a/src/SMAPI/Framework/StateTracking/ChestTracker.cs b/src/SMAPI/Framework/StateTracking/ChestTracker.cs index f907b9a5..66dc61eb 100644 --- a/src/SMAPI/Framework/StateTracking/ChestTracker.cs +++ b/src/SMAPI/Framework/StateTracking/ChestTracker.cs @@ -1,6 +1,9 @@ +using System; using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Events; +using StardewModdingAPI.Framework.StateTracking.Comparers; +using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewValley; using StardewValley.Objects; using ChangeType = StardewModdingAPI.Events.ChangeType; @@ -8,16 +11,22 @@ using ChangeType = StardewModdingAPI.Events.ChangeType; namespace StardewModdingAPI.Framework.StateTracking { /// <summary>Tracks changes to a chest's items.</summary> - internal class ChestTracker + internal class ChestTracker : IDisposable { /********* ** Fields *********/ - /// <summary>The chest's inventory as of the last reset.</summary> - private IDictionary<Item, int> PreviousInventory; + /// <summary>The item stack sizes as of the last update.</summary> + private readonly IDictionary<Item, int> StackSizes; - /// <summary>The chest's inventory change as of the last update.</summary> - private IDictionary<Item, int> CurrentInventory; + /// <summary>Items added since the last update.</summary> + private readonly HashSet<Item> Added = new HashSet<Item>(new ObjectReferenceComparer<Item>()); + + /// <summary>Items removed since the last update.</summary> + private readonly HashSet<Item> Removed = new HashSet<Item>(new ObjectReferenceComparer<Item>()); + + /// <summary>The underlying inventory watcher.</summary> + private readonly ICollectionWatcher<Item> InventoryWatcher; /********* @@ -35,50 +44,74 @@ namespace StardewModdingAPI.Framework.StateTracking public ChestTracker(Chest chest) { this.Chest = chest; - this.PreviousInventory = this.GetInventory(); + this.InventoryWatcher = WatcherFactory.ForNetList(chest.items); + + this.StackSizes = this.Chest.items + .Where(n => n != null) + .Distinct() + .ToDictionary(n => n, n => n.Stack); } /// <summary>Update the current values if needed.</summary> public void Update() { - this.CurrentInventory = this.GetInventory(); + // update watcher + this.InventoryWatcher.Update(); + foreach (Item item in this.InventoryWatcher.Added.Where(p => p != null)) + this.Added.Add(item); + foreach (Item item in this.InventoryWatcher.Removed.Where(p => p != null)) + { + if (!this.Added.Remove(item)) // item didn't change if it was both added and removed, so remove it from both lists + this.Removed.Add(item); + } + + // stop tracking removed stacks + foreach (Item item in this.Removed) + this.StackSizes.Remove(item); } /// <summary>Reset all trackers so their current values are the baseline.</summary> public void Reset() { - if (this.CurrentInventory != null) - this.PreviousInventory = this.CurrentInventory; + // update stack sizes + foreach (Item item in this.StackSizes.Keys.ToArray().Concat(this.Added)) + this.StackSizes[item] = item.Stack; + + // update watcher + this.InventoryWatcher.Reset(); + this.Added.Clear(); + this.Removed.Clear(); } /// <summary>Get the inventory changes since the last update.</summary> public IEnumerable<ItemStackChange> GetInventoryChanges() { - IDictionary<Item, int> previous = this.PreviousInventory; - IDictionary<Item, int> current = this.GetInventory(); + // removed + foreach (Item item in this.Removed) + yield return new ItemStackChange { Item = item, StackChange = -item.Stack, ChangeType = ChangeType.Removed }; + + // added + foreach (Item item in this.Added) + yield return new ItemStackChange { Item = item, StackChange = item.Stack, ChangeType = ChangeType.Added }; - foreach (Item item in previous.Keys.Union(current.Keys)) + // stack size changed + foreach (var entry in this.StackSizes) { - if (!previous.TryGetValue(item, out int prevStack)) - yield return new ItemStackChange { Item = item, StackChange = item.Stack, ChangeType = ChangeType.Added }; - else if (!current.TryGetValue(item, out int newStack)) - yield return new ItemStackChange { Item = item, StackChange = -item.Stack, ChangeType = ChangeType.Removed }; - else if (prevStack != newStack) - yield return new ItemStackChange { Item = item, StackChange = newStack - prevStack, ChangeType = ChangeType.StackChange }; + Item item = entry.Key; + int prevStack = entry.Value; + + if (item.Stack != prevStack) + yield return new ItemStackChange { Item = item, StackChange = item.Stack - prevStack, ChangeType = ChangeType.StackChange }; } } - - /********* - ** Private methods - *********/ - /// <summary>Get the player's current inventory.</summary> - private IDictionary<Item, int> GetInventory() + /// <summary>Release watchers and resources.</summary> + public void Dispose() { - return this.Chest.items - .Where(n => n != null) - .Distinct() - .ToDictionary(n => n, n => n.Stack); + this.StackSizes.Clear(); + this.Added.Clear(); + this.Removed.Clear(); + this.InventoryWatcher.Dispose(); } } } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index 314ff7f5..bde43486 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -82,6 +82,14 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers return new NetCollectionWatcher<T>(collection); } + /// <summary>Get a watcher for a net list.</summary> + /// <typeparam name="T">The value type.</typeparam> + /// <param name="collection">The net list.</param> + public static ICollectionWatcher<T> ForNetList<T>(NetList<T, NetRef<T>> collection) where T : class, INetObject<INetSerializable> + { + return new NetListWatcher<T>(collection); + } + /// <summary>Get a watcher for a net dictionary.</summary> /// <typeparam name="TKey">The dictionary key type.</typeparam> /// <typeparam name="TValue">The dictionary value type.</typeparam> diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 7445add9..519fe8f4 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -109,6 +109,9 @@ namespace StardewModdingAPI.Framework.StateTracking { foreach (IWatcher watcher in this.Watchers) watcher.Dispose(); + + foreach (var watcher in this.ChestWatchers.Values) + watcher.Dispose(); } @@ -124,7 +127,10 @@ namespace StardewModdingAPI.Framework.StateTracking foreach (KeyValuePair<Vector2, SObject> pair in removed) { if (pair.Value is Chest && this.ChestWatchers.TryGetValue(pair.Key, out ChestTracker watcher)) + { + watcher.Dispose(); this.ChestWatchers.Remove(pair.Key); + } } // add new watchers diff --git a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs index cd7d75ec..d25f3345 100644 --- a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs +++ b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs @@ -115,9 +115,12 @@ namespace StardewModdingAPI.Framework.StateTracking } } - /// <summary>Stop watching the player fields and release all references.</summary> + /// <summary>Release watchers and resources.</summary> public void Dispose() { + this.PreviousInventory.Clear(); + this.CurrentInventory?.Clear(); + foreach (IWatcher watcher in this.Watchers) watcher.Dispose(); } |