avoid Where() enumerators when possible

This commit is contained in:
Jesse Plamondon-Willard
2026-03-26 22:42:35 -04:00
parent da888ba9b0
commit db59d384b9
12 changed files with 110 additions and 59 deletions
+4 -1
View File
@@ -377,8 +377,11 @@ internal class InteractiveInstaller
{
// copy SMAPI files to game dir
this.PrintDebug("Adding SMAPI files...");
foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos())
{
if (!this.ShouldCopy(sourceEntry))
continue;
this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name), allowUserInput);
this.RecursiveCopy(sourceEntry, paths.GameDir);
}
@@ -329,8 +329,11 @@ public class ModScanner
{
bool hasXnbFile = false;
foreach (FileInfo file in files.Where(this.IsRelevant))
foreach (FileInfo file in files)
{
if (!this.IsRelevant(file))
continue;
if (this.StrictXnbModExtensions.Contains(file.Extension))
{
hasXnbFile = true;
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
namespace StardewModdingAPI.Web.Framework.Caching.Mods;
@@ -13,7 +12,7 @@ internal class ModCacheMemoryRepository : BaseCacheRepository, IModCacheReposito
** Fields
*********/
/// <summary>The cached mod data indexed by <c>{site key}:{ID}</c>.</summary>
private readonly IDictionary<string, Cached<IModPage>> Mods = new Dictionary<string, Cached<IModPage>>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Cached<IModPage>> Mods = new(StringComparer.OrdinalIgnoreCase);
/*********
@@ -57,13 +56,11 @@ internal class ModCacheMemoryRepository : BaseCacheRepository, IModCacheReposito
{
DateTimeOffset minDate = DateTimeOffset.UtcNow.Subtract(age);
string[] staleKeys = this.Mods
.Where(p => p.Value.LastRequested < minDate)
.Select(p => p.Key)
.ToArray();
foreach (string key in staleKeys)
this.Mods.Remove(key);
foreach ((string key, var modPage) in this.Mods)
{
if (modPage.LastRequested < minDate)
this.Mods.Remove(key);
}
}
+4 -1
View File
@@ -499,8 +499,11 @@ internal class ContentCoordinator : IDisposable
return this.ContentManagerLock.InReadLock(() =>
{
List<object> values = [];
foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName)))
foreach (IContentManager content in this.ContentManagers)
{
if (content.IsNamespaced || !content.IsLoaded(assetName))
continue;
object value = content.LoadExact<object>(assetName, useCache: true);
values.Add(value);
}
+3 -3
View File
@@ -281,7 +281,7 @@ internal class LogManager : IDisposable
/// <param name="logParanoidWarnings">Whether to log issues for mods which directly use potentially sensitive .NET APIs like file or shell access.</param>
/// <param name="logTechnicalDetailsForBrokenMods">Whether to include more technical details about broken mods in the TRACE logs. This is mainly useful for creating compatibility rewriters.</param>
/// <param name="hasHarmonyFix">Whether Harmony was fixed to work with Stardew Valley.</param>
public void LogModInfo(IModMetadata[] loaded, IModMetadata[] loadedContentPacks, IModMetadata[] loadedMods, IReadOnlyList<IModMetadata> skippedMods, bool logParanoidWarnings, bool logTechnicalDetailsForBrokenMods, bool hasHarmonyFix)
public void LogModInfo(IReadOnlyList<IModMetadata> loaded, IReadOnlyList<IModMetadata> loadedContentPacks, IReadOnlyList<IModMetadata> loadedMods, IReadOnlyList<IModMetadata> skippedMods, bool logParanoidWarnings, bool logTechnicalDetailsForBrokenMods, bool hasHarmonyFix)
{
// report malicious mod
foreach (IModMetadata mod in skippedMods)
@@ -300,7 +300,7 @@ internal class LogManager : IDisposable
}
// log loaded mods
this.Monitor.Log($"Loaded {loadedMods.Length} mods" + (loadedMods.Length > 0 ? ":" : "."), LogLevel.Info);
this.Monitor.Log($"Loaded {loadedMods.Count} mods" + (loadedMods.Count > 0 ? ":" : "."), LogLevel.Info);
foreach (IModMetadata metadata in loadedMods.OrderBy(p => p.DisplayName))
{
IManifest manifest = metadata.Manifest;
@@ -319,7 +319,7 @@ internal class LogManager : IDisposable
{
string? GetModDisplayName(string id) => loadedMods.FirstOrDefault(p => p.HasId(id))?.DisplayName;
this.Monitor.Log($"Loaded {loadedContentPacks.Length} content packs:", LogLevel.Info);
this.Monitor.Log($"Loaded {loadedContentPacks.Count} content packs:", LogLevel.Info);
foreach (IModMetadata metadata in loadedContentPacks.OrderBy(p => p.DisplayName))
{
IManifest manifest = metadata.Manifest;
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Mono.Cecil;
@@ -69,16 +68,19 @@ internal class RecursiveRewriter
/// <returns>Returns whether the module was modified.</returns>
public bool RewriteModule()
{
IEnumerable<TypeDefinition> types = this.Module.GetTypes().Where(type => type.BaseType != null); // skip special types like <Module>
bool changed = false;
try
{
changed |= this.RewriteModuleImpl(this.Module);
foreach (TypeDefinition type in types)
foreach (TypeDefinition type in this.Module.GetTypes())
{
if (type.BaseType is null)
continue; // skip special types like <Module>
changed |= this.RewriteTypeDefinition(type);
}
}
catch (Exception ex)
{
@@ -293,8 +295,7 @@ internal class RecursiveRewriter
MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType)
.Resolve()
?.Methods
.Where(method => method.IsConstructor)
.FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor));
.FirstOrDefault(method => method.IsConstructor && RewriteHelper.HasMatchingSignature(method, attribute.Constructor));
if (constructor == null)
throw new InvalidOperationException($"Can't rewrite attribute type '{attribute.AttributeType.FullName}' to '{newAttrType?.FullName}', no equivalent constructor found.");
@@ -225,10 +225,13 @@ internal class ModResolver
var states = mods.ToDictionary(mod => mod, _ => ModDependencyStatus.Queued);
// handle failed mods
foreach (IModMetadata mod in mods.Where(m => m.Status == ModMetadataStatus.Failed))
foreach (IModMetadata mod in mods)
{
states[mod] = ModDependencyStatus.Failed;
sortedMods.Push(mod);
if (mod.Status is ModMetadataStatus.Failed)
{
states[mod] = ModDependencyStatus.Failed;
sortedMods.Push(mod);
}
}
// sort mods
@@ -47,8 +47,11 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler
// get method definition
MethodDefinition? method = null;
foreach (MethodDefinition match in type.Methods.Where(p => p.Name == methodRef.Name))
foreach (MethodDefinition match in type.Methods)
{
if (match.Name != methodRef.Name)
continue;
// reference matches initial parameters of definition
if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match))
continue;
+39 -16
View File
@@ -484,10 +484,13 @@ internal class SCore : IDisposable
{
bool anyIgnored = false;
foreach (IModMetadata mod in mods.Where(p => p.IsIgnored))
foreach (IModMetadata mod in mods)
{
anyIgnored = true;
this.Monitor.Log($" Skipped {mod.GetRelativePathWithRoot()} (folder name starts with a dot).");
if (mod.IsIgnored)
{
anyIgnored = true;
this.Monitor.Log($" Skipped {mod.GetRelativePathWithRoot()} (folder name starts with a dot).");
}
}
if (anyIgnored)
@@ -1982,9 +1985,18 @@ internal class SCore : IDisposable
}
}
IModMetadata[] loaded = this.ModRegistry.GetAll().ToArray();
IModMetadata[] loadedContentPacks = loaded.Where(p => p.IsContentPack).ToArray();
IModMetadata[] loadedMods = loaded.Where(p => !p.IsContentPack).ToArray();
List<IModMetadata> loaded = [];
List<IModMetadata> loadedContentPacks = [];
List<IModMetadata> loadedMods = [];
foreach (IModMetadata mod in this.ModRegistry.GetAll())
{
loaded.Add(mod);
if (mod.IsContentPack)
loadedContentPacks.Add(mod);
else
loadedMods.Add(mod);
}
// unlock content packs
this.ModRegistry.AreAllModsLoaded = true;
@@ -2103,10 +2115,10 @@ internal class SCore : IDisposable
// validate dependencies
// Although dependencies are validated before mods are loaded, a dependency may have failed to load.
foreach (IManifestDependency dependency in manifest!.Dependencies.Where(p => p.IsRequired))
foreach (IManifestDependency dependency in manifest!.Dependencies)
{
// not missing
if (this.ModRegistry.Get(dependency.UniqueID) != null)
if (!dependency.IsRequired || this.ModRegistry.Get(dependency.UniqueID) != null)
continue;
// ignored in compatibility list (e.g. fully replaced by the game code)
@@ -2319,23 +2331,34 @@ internal class SCore : IDisposable
mod = null;
// find type
TypeInfo[] modEntries = modAssembly.DefinedTypes.Where(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract).Take(2).ToArray();
if (modEntries.Length == 0)
TypeInfo? modType = null;
foreach (TypeInfo type in modAssembly.DefinedTypes)
{
// skip: not a Mod type
if (!typeof(Mod).IsAssignableFrom(type) || type.IsAbstract)
continue;
// fail: multiple Mod types found
if (modType is not null)
{
error = $"its DLL contains multiple '{nameof(Mod)}' subclasses.";
return false;
}
// else save mod type
modType = type;
}
if (modType is null)
{
error = $"its DLL has no '{nameof(Mod)}' subclass.";
return false;
}
if (modEntries.Length > 1)
{
error = $"its DLL contains multiple '{nameof(Mod)}' subclasses.";
return false;
}
// get implementation
Context.HeuristicModsRunningCode.Push(metadata);
try
{
mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString());
mod = (Mod?)modAssembly.CreateInstance(modType.ToString());
}
finally
{
+8 -2
View File
@@ -192,8 +192,11 @@ internal class SMultiplayer : Multiplayer
newPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModContext, Game1.player.UniqueMultiplayerID, this.GetContextSyncMessageFields()));
// reply with other players' context
foreach (MultiplayerPeer otherPeer in this.Peers.Values.Where(p => p.PlayerID != newPeer.PlayerID))
foreach (MultiplayerPeer otherPeer in this.Peers.Values)
{
if (otherPeer.PlayerID == newPeer.PlayerID)
continue;
this.Monitor.VerboseLog($" Replying with context for player {otherPeer.PlayerID}...");
newPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModContext, otherPeer.PlayerID, this.GetContextSyncMessageFields(otherPeer)));
}
@@ -202,8 +205,11 @@ internal class SMultiplayer : Multiplayer
if (this.Peers.Count > 1)
{
object[] fields = this.GetContextSyncMessageFields(newPeer);
foreach (MultiplayerPeer otherPeer in this.Peers.Values.Where(p => p.PlayerID != newPeer.PlayerID))
foreach (MultiplayerPeer otherPeer in this.Peers.Values)
{
if (otherPeer.PlayerID == newPeer.PlayerID)
continue;
this.Monitor.VerboseLog($" Forwarding context to player {otherPeer.PlayerID}...");
otherPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModContext, newPeer.PlayerID, fields));
}
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using StardewModdingAPI.Framework.StateTracking.Comparers;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
@@ -16,7 +15,7 @@ internal class ChestTracker : IDisposable
** Fields
*********/
/// <summary>The item stack sizes as of the last update.</summary>
private readonly IDictionary<Item, int> StackSizes;
private readonly Dictionary<Item, int> StackSizes = [];
/// <summary>Items added since the last update.</summary>
private readonly HashSet<Item> Added = new(new ObjectReferenceComparer<Item>());
@@ -46,9 +45,11 @@ internal class ChestTracker : IDisposable
this.Chest = chest;
this.InventoryWatcher = WatcherFactory.ForInventory($"{name}.{nameof(chest.Items)}", chest.Items);
this.StackSizes = this.Chest.Items
.Where(n => n != null)
.ToDictionary(n => n, n => n.Stack);
foreach (Item? item in this.Chest.Items)
{
if (item is not null)
this.StackSizes[item] = item.Stack;
}
}
/// <summary>Update the current values if needed.</summary>
@@ -16,10 +16,10 @@ internal class PlayerTracker : IDisposable
** Fields
*********/
/// <summary>The player's inventory as of the last reset.</summary>
private IDictionary<Item, int> PreviousInventory;
private readonly Dictionary<Item, int> PreviousInventory;
/// <summary>The player's inventory change as of the last update.</summary>
private IDictionary<Item, int> CurrentInventory;
private readonly Dictionary<Item, int> CurrentInventory = [];
/// <summary>The player's last valid location.</summary>
private GameLocation? LastValidLocation;
@@ -50,7 +50,7 @@ internal class PlayerTracker : IDisposable
{
// init player data
this.Player = player;
this.CurrentInventory = this.GetInventory();
this.SaveInventoryTo(this.CurrentInventory);
this.PreviousInventory = new Dictionary<Item, int>(this.CurrentInventory);
// init trackers
@@ -81,16 +81,21 @@ internal class PlayerTracker : IDisposable
watcher.Update();
// update inventory
this.CurrentInventory = this.GetInventory();
this.CurrentInventory.Clear();
this.SaveInventoryTo(this.CurrentInventory);
}
/// <summary>Reset all trackers so their current values are the baseline.</summary>
public void Reset()
{
// reset watchers
foreach (IWatcher watcher in this.Watchers)
watcher.Reset();
this.PreviousInventory = this.CurrentInventory;
// reset PreviousInventory to match current
this.PreviousInventory.Clear();
foreach ((Item item, int stack) in this.CurrentInventory)
this.PreviousInventory.Add(item, stack);
}
/// <summary>Get the player's current location, ignoring temporary null values.</summary>
@@ -105,8 +110,8 @@ internal class PlayerTracker : IDisposable
/// <returns>Returns whether anything changed.</returns>
public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes)
{
ISet<Item> added = new HashSet<Item>(new ObjectReferenceComparer<Item>());
ISet<Item> removed = new HashSet<Item>(new ObjectReferenceComparer<Item>());
HashSet<Item> added = new(new ObjectReferenceComparer<Item>());
HashSet<Item> removed = new(new ObjectReferenceComparer<Item>());
foreach (Item item in this.PreviousInventory.Keys.Union(this.CurrentInventory.Keys))
{
if (!this.PreviousInventory.ContainsKey(item))
@@ -133,10 +138,13 @@ internal class PlayerTracker : IDisposable
** Private methods
*********/
/// <summary>Get the player's current inventory.</summary>
private IDictionary<Item, int> GetInventory()
/// <param name="inventory">The dictionary to fill with the player's inventory.</param>
private void SaveInventoryTo(Dictionary<Item, int> inventory)
{
return this.Player.Items
.Where(n => n != null)
.ToDictionary(n => n, n => n.Stack);
foreach (Item? item in this.Player.Items)
{
if (item is not null)
inventory[item] = item.Stack;
}
}
}