This commit...
- avoids repeated collection count accesses since we can just track the result while updating;
- and adds more short-circuiting in cases where we know watchers haven't changed.
This commit...
- removes the Lazy<T>, which was unnecessary since mods will virtually always check which buttons were changed;
- avoids a dictionary lookup on each property access;
- and avoids allocating LINQ enumerators, temporary array, and dictionary on init.
Some quick benchmarks show that...
- `new HashSet<T>(values)` (266ns) is 79% faster than `values.ToImmutableHashSet()` (1440ns);
- filling a hash set with a loop (554ns) is 21% faster than filling it from a LINQ enumerable (697ns);
- and searching a `HashSet` (18ns) is 49% faster than searching an `ImmutableHashSet` (35ns).
These cover very rare edge cases with invalid inventory data, so it's not really worth the added performance hit of handling them. The next commits will avoid the error if duplicates do occur.
This replaces `ButtonStates` dictionaries with `PressedButtons` sets, which...
- reduces allocations (since most buttons aren't down at any given time);
- reduces iteration (and a yield generator in `GamePadStateBuilder.GetPressedGamePadButtons()`) for filling pressed buttons;
- and simplifies change tracking.
The draft releases caused issues for SMAPI's update flow, because...
1. Updates are releases to Nexus first, since that doesn't trigger update alerts. If there are no major issues, they're released to GitHub a few days later.
2. Update alerts work by fetching the last two SMAPI GitHub releases. The GitHub API doesn't allow ignoring drafts, so SMAPI just ignores any drafts in those top two slots.
3. The new build workflow created a draft release for each version tag.
That meant that releasing a second SMAPI update to Nexus before the GitHub release (like 4.5.0 + 4.5.1) would break update alerts for all players with a "Found no GitHub release for this ID" error, since the two latest releases were both ignored.