Skip to content

Commit 45216fe

Browse files
committed
reduce allocations in collection watchers
1 parent aaa23ed commit 45216fe

3 files changed

Lines changed: 49 additions & 17 deletions

File tree

src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using StardewValley.Extensions;
23

34
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers;
45

@@ -15,6 +16,9 @@ internal class ComparableListWatcher<TValue> : BaseDisposableWatcher, ICollectio
1516
/// <summary>The values during the previous update.</summary>
1617
private HashSet<TValue> LastValues;
1718

19+
/// <summary>A pooled set used to update <see cref="LastValues"/> each update.</summary>
20+
private HashSet<TValue> NewValues;
21+
1822
/// <summary>The pairs added since the last reset.</summary>
1923
private readonly List<TValue> AddedImpl = [];
2024

@@ -50,6 +54,7 @@ public ComparableListWatcher(string name, ICollection<TValue> values, IEqualityC
5054
this.Name = name;
5155
this.CurrentValues = values;
5256
this.LastValues = new HashSet<TValue>(comparer);
57+
this.NewValues = new HashSet<TValue>(comparer);
5358
}
5459

5560
/// <inheritdoc />
@@ -68,19 +73,24 @@ public void Update()
6873
return;
6974
}
7075

76+
// get new values
77+
this.NewValues.Clear();
78+
this.NewValues.AddRange(this.CurrentValues);
79+
7180
// detect changes
72-
HashSet<TValue> curValues = new HashSet<TValue>(this.CurrentValues, this.LastValues.Comparer);
7381
foreach (TValue value in this.LastValues)
7482
{
75-
if (!curValues.Contains(value))
83+
if (!this.NewValues.Contains(value))
7684
this.RemovedImpl.Add(value);
7785
}
78-
foreach (TValue value in curValues)
86+
foreach (TValue value in this.NewValues)
7987
{
8088
if (!this.LastValues.Contains(value))
8189
this.AddedImpl.Add(value);
8290
}
83-
this.LastValues = curValues;
91+
92+
// save result
93+
(this.LastValues, this.NewValues) = (this.NewValues, this.LastValues); // reuse the now-unused previous 'LastValues' set on the next update
8494
}
8595

8696
/// <inheritdoc />

src/SMAPI/Framework/StateTracking/FieldWatchers/InventoryWatcher.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using StardewModdingAPI.Framework.StateTracking.Comparers;
33
using StardewValley;
4+
using StardewValley.Extensions;
45
using StardewValley.Inventories;
56

67
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers;
@@ -15,10 +16,16 @@ internal class InventoryWatcher : BaseDisposableWatcher, ICollectionWatcher<Item
1516
private readonly Inventory Inventory;
1617

1718
/// <summary>The pairs added since the last reset.</summary>
18-
private readonly ISet<Item> AddedImpl = new HashSet<Item>(new ObjectReferenceComparer<Item>());
19+
private readonly HashSet<Item> AddedImpl = new(new ObjectReferenceComparer<Item>());
1920

2021
/// <summary>The pairs removed since the last reset.</summary>
21-
private readonly ISet<Item> RemovedImpl = new HashSet<Item>(new ObjectReferenceComparer<Item>());
22+
private readonly HashSet<Item> RemovedImpl = new(new ObjectReferenceComparer<Item>());
23+
24+
/// <summary>A pooled set used to track the previous inventory when detecting changes.</summary>
25+
private readonly HashSet<Item> PooledOldSet = new(new ObjectReferenceComparer<Item>());
26+
27+
/// <summary>A pooled set used to track the new inventory when detecting changes.</summary>
28+
private readonly HashSet<Item> PooledNewSet = new(new ObjectReferenceComparer<Item>());
2229

2330

2431
/*********
@@ -87,17 +94,21 @@ public override void Dispose()
8794
/// <param name="newValues">The new list of values.</param>
8895
private void OnInventoryReplaced(Inventory inventory, IList<Item> oldValues, IList<Item> newValues)
8996
{
90-
ISet<Item> oldSet = new HashSet<Item>(oldValues, new ObjectReferenceComparer<Item>());
91-
ISet<Item> changed = new HashSet<Item>(newValues, new ObjectReferenceComparer<Item>());
97+
this.PooledOldSet.Clear();
98+
this.PooledOldSet.AddRange(oldValues);
9299

93-
foreach (Item value in oldSet)
100+
this.PooledNewSet.Clear();
101+
this.PooledNewSet.AddRange(newValues);
102+
103+
foreach (Item value in this.PooledOldSet)
94104
{
95-
if (!changed.Contains(value))
105+
if (!this.PooledNewSet.Contains(value))
96106
this.Remove(value);
97107
}
98-
foreach (Item value in changed)
108+
109+
foreach (Item value in this.PooledNewSet)
99110
{
100-
if (!oldSet.Contains(value))
111+
if (!this.PooledOldSet.Contains(value))
101112
this.Add(value);
102113
}
103114
}

src/SMAPI/Framework/StateTracking/PlayerTracker.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ internal class PlayerTracker : IDisposable
2020
/// <summary>The player's inventory change as of the last update.</summary>
2121
private readonly Dictionary<Item, int> CurrentInventory = [];
2222

23+
/// <summary>A pooled set used to track the added items when detecting changes.</summary>
24+
private readonly HashSet<Item> PooledAdded;
25+
26+
/// <summary>A pooled set used to track the removed items when detecting changes.</summary>
27+
private readonly HashSet<Item> PooledRemoved;
28+
2329
/// <summary>The player's last valid location.</summary>
2430
private GameLocation? LastValidLocation;
2531

@@ -67,6 +73,11 @@ public PlayerTracker(Farmer player)
6773
// track watchers for convenience
6874
this.Watchers.Add(this.LocationWatcher);
6975
this.Watchers.AddRange(this.SkillWatchers.Values);
76+
77+
// init pooled sets
78+
var comparer = new ObjectReferenceComparer<Item>();
79+
this.PooledAdded = new HashSet<Item>(comparer);
80+
this.PooledRemoved = new HashSet<Item>(comparer);
7081
}
7182

7283
/// <summary>Update the current values if needed.</summary>
@@ -109,22 +120,22 @@ public void Reset()
109120
/// <returns>Returns whether anything changed.</returns>
110121
public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes)
111122
{
112-
HashSet<Item> added = new(new ObjectReferenceComparer<Item>());
113-
HashSet<Item> removed = new(new ObjectReferenceComparer<Item>());
123+
this.PooledAdded.Clear();
124+
this.PooledRemoved.Clear();
114125

115126
foreach (Item item in this.PreviousInventory.Keys)
116127
{
117128
if (!this.CurrentInventory.ContainsKey(item))
118-
removed.Add(item);
129+
this.PooledRemoved.Add(item);
119130
}
120131

121132
foreach (Item item in this.CurrentInventory.Keys)
122133
{
123134
if (!this.PreviousInventory.ContainsKey(item))
124-
added.Add(item);
135+
this.PooledAdded.Add(item);
125136
}
126137

127-
return SnapshotItemListDiff.TryGetChanges(added: added, removed: removed, stackSizes: this.PreviousInventory, out changes);
138+
return SnapshotItemListDiff.TryGetChanges(added: this.PooledAdded, removed: this.PooledRemoved, stackSizes: this.PreviousInventory, out changes);
128139
}
129140

130141
/// <summary>Release watchers and resources.</summary>

0 commit comments

Comments
 (0)