From 844efa32d49baa2ca58332cd26bbbe3cc772ad22 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 1 Jan 2020 00:13:59 -0500 Subject: [PATCH] optimize chest watchers using net events --- .../Framework/StateTracking/ChestTracker.cs | 89 +++++++++++++------ .../FieldWatchers/WatcherFactory.cs | 8 ++ .../StateTracking/LocationTracker.cs | 6 ++ .../Framework/StateTracking/PlayerTracker.cs | 5 +- 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 { /// Tracks changes to a chest's items. - internal class ChestTracker + internal class ChestTracker : IDisposable { /********* ** Fields *********/ - /// The chest's inventory as of the last reset. - private IDictionary PreviousInventory; + /// The item stack sizes as of the last update. + private readonly IDictionary StackSizes; - /// The chest's inventory change as of the last update. - private IDictionary CurrentInventory; + /// Items added since the last update. + private readonly HashSet Added = new HashSet(new ObjectReferenceComparer()); + + /// Items removed since the last update. + private readonly HashSet Removed = new HashSet(new ObjectReferenceComparer()); + + /// The underlying inventory watcher. + private readonly ICollectionWatcher 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); } /// Update the current values if needed. 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); } /// Reset all trackers so their current values are the baseline. 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(); } /// Get the inventory changes since the last update. public IEnumerable GetInventoryChanges() { - IDictionary previous = this.PreviousInventory; - IDictionary current = this.GetInventory(); + // removed + foreach (Item item in this.Removed) + yield return new ItemStackChange { Item = item, StackChange = -item.Stack, ChangeType = ChangeType.Removed }; - foreach (Item item in previous.Keys.Union(current.Keys)) + // added + foreach (Item item in this.Added) + yield return new ItemStackChange { Item = item, StackChange = item.Stack, ChangeType = ChangeType.Added }; + + // 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 - *********/ - /// Get the player's current inventory. - private IDictionary GetInventory() + /// Release watchers and resources. + 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(collection); } + /// Get a watcher for a net list. + /// The value type. + /// The net list. + public static ICollectionWatcher ForNetList(NetList> collection) where T : class, INetObject + { + return new NetListWatcher(collection); + } + /// Get a watcher for a net dictionary. /// The dictionary key type. /// The dictionary value type. 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 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 } } - /// Stop watching the player fields and release all references. + /// Release watchers and resources. public void Dispose() { + this.PreviousInventory.Clear(); + this.CurrentInventory?.Clear(); + foreach (IWatcher watcher in this.Watchers) watcher.Dispose(); }