avoid Any() on warmer paths

This is typically much slower than equivalent manual checks.
This commit is contained in:
Jesse Plamondon-Willard
2026-03-27 23:59:33 -04:00
parent 7bc3595e66
commit 2194fa6f28
21 changed files with 154 additions and 48 deletions
@@ -80,7 +80,7 @@ public class ChangeDescriptor
this.Apply(values);
// format
if (rawField == null && !values.Any())
if (rawField == null && values.Count == 0)
return null;
return string.Join(", ", values);
}
@@ -91,7 +91,7 @@ public class ChangeDescriptor
public void Apply(List<string> values)
{
// replace/remove values
if (this.Replace.Any() || this.Remove.Any())
if (this.Replace.Count > 0 || this.Remove.Count > 0)
{
for (int i = values.Count - 1; i >= 0; i--)
{
@@ -106,7 +106,7 @@ public class ChangeDescriptor
}
// add values
if (this.Add.Any())
if (this.Add.Count > 0)
{
HashSet<string> curValues = new(values.Select(p => p.Trim()), StringComparer.OrdinalIgnoreCase);
foreach (string add in this.Add)
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Toolkit.Framework;
@@ -37,7 +36,7 @@ public static class ManifestValidator
// validate EntryDll/ContentPackFor format
if (hasDll)
{
if (manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any())
if (manifest.EntryDll!.IndexOfAny(Path.GetInvalidFileNameChars()) > -1)
{
error = $"manifest has invalid filename '{manifest.EntryDll}' for the {nameof(IManifest.EntryDll)} field.";
return false;
@@ -63,7 +62,7 @@ public static class ManifestValidator
if (string.IsNullOrWhiteSpace(manifest.UniqueID))
missingFields.Add(nameof(IManifest.UniqueID));
if (missingFields.Any())
if (missingFields.Count > 0)
{
error = $"manifest is missing required fields ({string.Join(", ", missingFields)}).";
return false;
@@ -1,5 +1,3 @@
using System.Linq;
namespace StardewModdingAPI.Toolkit.Framework.ModData;
/// <summary>A versioned mod metadata field.</summary>
@@ -66,7 +64,13 @@ public class ModDataField
{
// update key
case ModDataFieldKey.UpdateKey:
return manifest.UpdateKeys.Any(p => !string.IsNullOrWhiteSpace(p));
foreach (string updateKey in manifest.UpdateKeys)
{
if (!string.IsNullOrWhiteSpace(updateKey))
return true;
}
return false;
// non-manifest fields
case ModDataFieldKey.StatusReasonPhrase:
@@ -138,7 +138,7 @@ public class ModScanner
return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyVortexFolder, "it's an empty Vortex folder (is the mod disabled in Vortex?).");
// empty folder
if (!relevantFiles.Any())
if (relevantFiles.Length == 0)
return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyFolder, "it's an empty folder.");
// XNB mod
@@ -290,7 +290,7 @@ public class ModScanner
DirectoryInfo[] subfolders = folder.GetDirectories().Where(this.IsRelevant).ToArray();
FileInfo[] files = folder.GetFiles().Where(this.IsRelevant).ToArray();
return subfolders.Any() && !files.Any();
return subfolders.Length > 0 && files.Length == 0;
}
/// <summary>Recursively get all files in a folder.</summary>
@@ -320,7 +320,13 @@ public class ModScanner
return false;
// ignored entry name
return !this.IgnoreFilesystemNames.Any(p => p.IsMatch(entry.Name));
foreach (Regex rule in this.IgnoreFilesystemNames)
{
if (rule.IsMatch(entry.Name))
return false;
}
return true;
}
/// <summary>Get whether a set of files looks like an XNB mod.</summary>
+1 -1
View File
@@ -93,7 +93,7 @@ public static class PathUtilities
}
// keep trailing separator
if ((!hasRoot || segments.Any()) && PathUtilities.PossiblePathSeparators.Contains(path[path.Length - 1]))
if ((!hasRoot || segments.Length > 0) && PathUtilities.PossiblePathSeparators.Contains(path[path.Length - 1]))
newPath += PathUtilities.PreferredPathSeparator;
return newPath;
+2 -1
View File
@@ -10,6 +10,7 @@ using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit.Framework;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
using StardewValley.Extensions;
namespace StardewModdingAPI;
@@ -298,7 +299,7 @@ public static class Constants
/// <param name="targetPlatform">The target game platform.</param>
internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform)
{
var removeAssemblyReferences = new List<string>();
var removeAssemblyReferences = new HashSet<string>();
var targetAssemblies = new List<Assembly>();
// get assembly renamed in SMAPI 3.0
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
@@ -199,7 +198,7 @@ internal abstract class BaseContentManager : LocalizedContentManager, IContentMa
// throwing other types like ArgumentException here.
if (string.IsNullOrWhiteSpace(assetName))
throw new SContentLoadException(ContentLoadErrorType.InvalidName, "The asset key or local path is empty.");
if (assetName.Intersect(Path.GetInvalidPathChars()).Any())
if (assetName.IndexOfAny(Path.GetInvalidPathChars()) > -1)
throw new SContentLoadException(ContentLoadErrorType.InvalidName, "The asset key or local path contains invalid characters.");
return this.Cache.NormalizeKey(assetName);
@@ -312,8 +312,13 @@ internal sealed class ModContentManager : BaseContentManager
/// <exception cref="SContentLoadException">The <typeparamref name="TAsset"/> is not compatible with any of the <paramref name="validTypes"/>.</exception>
private void AssertValidType<TAsset>(IAssetName assetName, FileInfo file, params Type[] validTypes)
{
if (!validTypes.Any(validType => validType.IsAssignableFrom(typeof(TAsset))))
this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'.");
foreach (Type type in validTypes)
{
if (type.IsAssignableFrom(typeof(TAsset)))
return;
}
this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'.");
}
/// <summary>Throw an error which indicates that an asset couldn't be loaded.</summary>
+11 -2
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Extensions;
@@ -174,7 +173,17 @@ internal class ManagedEvent<TEventArgs> : IManagedEvent
{
// recheck priorities
if (this.HasRemovedHandlers)
this.HasPriorities = this.Handlers.Any(p => p.Priority != EventPriority.Normal);
{
this.HasPriorities = false;
foreach (var handler in this.Handlers)
{
if (handler.Priority != EventPriority.Normal)
{
this.HasPriorities = true;
break;
}
}
}
// sort by priority if needed
if (this.HasPriorities)
+7 -2
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using StardewValley;
@@ -211,7 +210,13 @@ internal sealed class SInputState : InputState
/// <param name="buttons">The buttons to check.</param>
public bool IsAnyDown(InputButton[] buttons)
{
return buttons.Any(button => this.IsDown(button.ToSButton()));
foreach (InputButton button in buttons)
{
if (this.IsDown(button.ToSButton()))
return true;
}
return false;
}
/// <summary>Get the state of a button.</summary>
@@ -336,7 +336,7 @@ internal class AssemblyLoader : IDisposable
for (int i = 0; i < module.AssemblyReferences.Count; i++)
{
// remove old assembly reference
if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
if (this.AssemblyMap.RemoveNames.Contains(module.AssemblyReferences[i].Name))
{
platformChanged = true;
module.AssemblyReferences.RemoveAt(i);
@@ -267,14 +267,22 @@ internal static class RewriteHelper
{
if (reference.Name == ".ctor")
{
return type
.GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public)
.Any(method => RewriteHelper.HasMatchingSignature(method, reference));
foreach (ConstructorInfo constructor in type.GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public))
{
if (RewriteHelper.HasMatchingSignature(constructor, reference))
return true;
}
}
else
{
foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public))
{
if (RewriteHelper.HasMatchingSignature(method, reference))
return true;
}
}
return type
.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public)
.Any(method => RewriteHelper.HasMatchingSignature(method, reference));
return false;
}
/****
+11 -2
View File
@@ -241,14 +241,23 @@ internal class ModMetadata : IModMetadata
/// <inheritdoc />
public bool HasValidUpdateKeys()
{
return this.GetUpdateKeys(validOnly: true).Any();
foreach (UpdateKey _ in this.GetUpdateKeys(validOnly: true))
return true;
return false;
}
/// <inheritdoc />
public bool HasWarnings(params ModWarning[] warnings)
{
ModWarning curWarnings = this.Warnings;
return warnings.Any(warning => curWarnings.HasFlag(warning));
foreach (ModWarning matchWarning in warnings)
{
if (curWarnings.HasFlag(matchWarning))
return true;
}
return false;
}
/// <inheritdoc />
@@ -20,7 +20,7 @@ internal class PlatformAssemblyMap : IDisposable
public readonly Platform TargetPlatform;
/// <summary>The short assembly names to remove as assembly reference, and replace with the <see cref="Targets"/>. These should be short names (like "Stardew Valley").</summary>
public readonly IReadOnlyList<string> RemoveNames;
public readonly HashSet<string> RemoveNames;
/****
** Metadata
@@ -42,7 +42,7 @@ internal class PlatformAssemblyMap : IDisposable
/// <param name="targetPlatform">The target game platform.</param>
/// <param name="removeAssemblyNames">The assembly short names to remove (like <c>Stardew Valley</c>).</param>
/// <param name="targetAssemblies">The assemblies to target.</param>
public PlatformAssemblyMap(Platform targetPlatform, IReadOnlyList<string> removeAssemblyNames, IReadOnlyList<Assembly> targetAssemblies)
public PlatformAssemblyMap(Platform targetPlatform, HashSet<string> removeAssemblyNames, IReadOnlyList<Assembly> targetAssemblies)
{
// save data
this.TargetPlatform = targetPlatform;
@@ -70,8 +70,13 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler
var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count)
.Select(p => RewriteHelper.GetLoadValueInstruction(p.Constant))
.ToArray();
if (loadInstructions.Any(p => p == null))
return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized
// skip if invalid
foreach (Instruction? loadInstruction in loadInstructions)
{
if (loadInstruction is null)
return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized
}
// rewrite method reference
foreach (Instruction? loadInstruction in loadInstructions)
+2 -2
View File
@@ -1011,8 +1011,8 @@ internal class SCore : IDisposable
if (verbose)
{
string addedText = added.Any() ? string.Join(", ", added.Select(p => p.Name)) : "none";
string removedText = removed.Any() ? string.Join(", ", removed.Select(p => p.Name)) : "none";
string addedText = added.Count > 0 ? string.Join(", ", added.Select(p => p.Name)) : "none";
string removedText = removed.Count > 0 ? string.Join(", ", removed.Select(p => p.Name)) : "none";
this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", Monitor.ContextLogLevel);
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
@@ -20,6 +19,9 @@ internal class LocationTracker : IWatcher
/// <summary>The underlying watchers.</summary>
private readonly List<IWatcher> Watchers = [];
/// <summary>Whether any of the <see cref="Watchers"/> changed.</summary>
private bool WatchersHaveChanges;
/*********
** Accessors
@@ -28,7 +30,7 @@ internal class LocationTracker : IWatcher
public string Name { get; }
/// <inheritdoc />
public bool IsChanged => this.Watchers.Any(p => p.IsChanged);
public bool IsChanged => this.WatchersHaveChanges;
/// <summary>The tracked location.</summary>
public GameLocation Location { get; }
@@ -93,9 +95,15 @@ internal class LocationTracker : IWatcher
/// <inheritdoc />
public void Update()
{
this.WatchersHaveChanges = false;
foreach (IWatcher watcher in this.Watchers)
{
watcher.Update();
if (watcher.IsChanged)
this.WatchersHaveChanges = true;
}
this.UpdateChestWatcherList(added: this.ObjectsWatcher.Added, removed: this.ObjectsWatcher.Removed);
foreach (var watcher in this.ChestWatchers)
@@ -105,6 +113,8 @@ internal class LocationTracker : IWatcher
/// <inheritdoc />
public void Reset()
{
this.WatchersHaveChanges = false;
foreach (IWatcher watcher in this.Watchers)
watcher.Reset();
@@ -118,7 +128,7 @@ internal class LocationTracker : IWatcher
foreach (IWatcher watcher in this.Watchers)
watcher.Dispose();
foreach (var watcher in this.ChestWatchers.Values)
foreach (ChestTracker watcher in this.ChestWatchers.Values)
watcher.Dispose();
}
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using StardewModdingAPI.Framework.StateTracking.Comparers;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
@@ -33,6 +32,9 @@ internal class WorldLocationsTracker : IWatcher
/// <summary>The pooled list instance for <see cref="GetLocationsWhoseBuildingsChanged"/>.</summary>
private static readonly List<LocationTracker> PooledLocationsWithBuildingsChanged = [];
/// <summary>Whether any of the <see cref="Locations"/> have content changes.</summary>
private bool LocationsHaveChanges;
/*********
** Accessors
@@ -44,7 +46,7 @@ internal class WorldLocationsTracker : IWatcher
public bool IsLocationListChanged => this.Added.Count > 0 || this.Removed.Count > 0;
/// <inheritdoc />
public bool IsChanged => this.IsLocationListChanged || this.Locations.Any(p => p.IsChanged);
public bool IsChanged => this.IsLocationListChanged || this.LocationsHaveChanges;
/// <summary>The tracked locations.</summary>
public IEnumerable<LocationTracker> Locations => this.LocationDict.Values;
@@ -77,8 +79,15 @@ internal class WorldLocationsTracker : IWatcher
this.LocationListWatcher.Update();
this.MineLocationListWatcher.Update();
this.VolcanoLocationListWatcher.Update();
// update location content watchers
this.LocationsHaveChanges = false;
foreach (LocationTracker watcher in this.Locations)
{
watcher.Update();
if (watcher.IsChanged)
this.LocationsHaveChanges = true;
}
// detect added/removed locations
if (this.LocationListWatcher.IsChanged)
@@ -131,6 +140,8 @@ internal class WorldLocationsTracker : IWatcher
/// <inheritdoc />
public void Reset()
{
this.LocationsHaveChanges = false;
this.ResetLocationList();
foreach (IWatcher watcher in this.GetWatchers())
watcher.Reset();
+12 -1
View File
@@ -177,7 +177,18 @@ internal class CoreAssetPropagator
this.UpdateMap(info);
var newWarps = GetWarpSet(location);
changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count;
if (!changedWarpRoutes)
{
foreach (string oldWarp in oldWarps)
{
if (!newWarps.Contains(oldWarp))
{
changedWarpRoutes = true;
break;
}
}
}
changed = true;
}
}
+20 -3
View File
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using Microsoft.Xna.Framework.Input;
using StardewValley;
@@ -697,13 +696,31 @@ public static class SButtonExtensions
/// <summary>Get whether the given button is equivalent to <see cref="Options.useToolButton"/>.</summary>
public bool IsUseToolButton()
{
return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input);
if (input is SButton.ControllerX)
return true;
foreach (InputButton button in Game1.options.useToolButton)
{
if (button.ToSButton() == input)
return true;
}
return false;
}
/// <summary>Get whether the given button is equivalent to <see cref="Options.actionButton"/>.</summary>
public bool IsActionButton()
{
return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input);
if (input is SButton.ControllerA)
return true;
foreach (InputButton button in Game1.options.actionButton)
{
if (button.ToSButton() == input)
return true;
}
return false;
}
}
}
+9 -2
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using StardewModdingAPI.Framework;
namespace StardewModdingAPI.Utilities;
@@ -36,7 +35,15 @@ public class Keybind
public Keybind(params SButton[] buttons)
{
this.Buttons = buttons;
this.IsBound = buttons.Any(p => p != SButton.None);
foreach (SButton button in buttons)
{
if (button != SButton.None)
{
this.IsBound = true;
break;
}
}
}
/// <summary>Parse a keybind string, if it's valid.</summary>