reduce remaining uses of LINQ on warmer paths

This commit is contained in:
Jesse Plamondon-Willard
2026-03-27 23:59:33 -04:00
parent 06ebc8d049
commit d9d33c5949
19 changed files with 149 additions and 84 deletions
+1 -2
View File
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Framework.Content;
@@ -117,7 +116,7 @@ internal class ContentCache
this.Remove(key, dispose);
return removed.Count == 0
? Enumerable.Empty<string>() // let GC collect the list in gen0 instead of potentially living longer
? Array.Empty<string>() // let GC collect the list in gen0 instead of potentially living longer
: removed;
}
}
@@ -170,7 +170,7 @@ internal class GameContentManager : BaseContentManager
return null;
}
loader = loadOperations.OrderByDescending(p => p.Priority).FirstOrDefault();
loader = loadOperations.MaxBy(p => p.Priority);
}
if (loader == null)
return null;
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using StardewValley.Menus;
namespace StardewModdingAPI.Framework.Extensions;
@@ -13,13 +12,12 @@ internal static class ClickableMenuExtensions
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
public string GetMenuChainLabel()
{
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
{
for (; menu != null; menu = menu.GetParentMenu())
yield return menu;
}
Stack<string> chain = [];
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
for (; menu != null; menu = menu.GetParentMenu())
chain.Push(menu.GetType().FullName!);
return string.Join(" > ", chain);
}
}
}
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Exceptions;
@@ -114,12 +114,14 @@ internal class AssemblyLoader : IDisposable
// get referenced local assemblies
AssemblyParseResult[] assemblies;
{
HashSet<string> visitedAssemblyNames = new( // don't try loading assemblies that are already loaded
from assembly in AppDomain.CurrentDomain.GetAssemblies()
let name = assembly.GetName().Name
where name != null
select name
);
// don't try loading assemblies that are already loaded
HashSet<string> visitedAssemblyNames = [];
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
string? name = assembly.GetName().Name;
if (name != null)
visitedAssemblyNames.Add(name);
}
assemblies = this.GetReferencedLocalAssemblies(assemblyFile, visitedAssemblyNames, this.AssemblyDefinitionResolver).ToArray();
}
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using StardewModdingAPI.Framework.ModLoading.Framework;
@@ -41,13 +40,25 @@ internal class ReferenceToInvalidMemberFinder : BaseInstructionHandler
FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction);
if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType))
{
FieldDefinition? targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name);
// resolve field
FieldDefinition? targetField = null;
if (fieldRef.DeclaringType.Resolve() is { } declaringType)
{
foreach (FieldDefinition field in declaringType.Fields)
{
if (field.Name == fieldRef.Name)
{
targetField = field;
break;
}
}
}
// wrong return type
// detect wrong return type
if (targetField != null && !RewriteHelper.LooksLikeSameType(fieldRef.FieldType, targetField.FieldType))
this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {this.GetMemberDisplayName(fieldRef)} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})");
// missing
// detect missing
else if (targetField == null || targetField.HasConstant || !RewriteHelper.HasSameNamespaceAndName(fieldRef.DeclaringType, targetField.DeclaringType))
this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {this.GetMemberDisplayName(fieldRef)} (no such field)");
@@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
@@ -292,10 +291,18 @@ internal class RecursiveRewriter
if (curChanged)
{
// get constructor
MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType)
.Resolve()
?.Methods
.FirstOrDefault(method => method.IsConstructor && RewriteHelper.HasMatchingSignature(method, attribute.Constructor));
MethodDefinition? constructor = null;
if ((newAttrType ?? attribute.AttributeType).Resolve() is { } typeDef)
{
foreach (MethodDefinition method in typeDef.Methods)
{
if (method.IsConstructor && RewriteHelper.HasMatchingSignature(method, attribute.Constructor))
{
constructor = method;
break;
}
}
}
if (constructor == null)
throw new InvalidOperationException($"Can't rewrite attribute type '{attribute.AttributeType.FullName}' to '{newAttrType?.FullName}', no equivalent constructor found.");
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using StardewModdingAPI.Framework.ModLoading.Framework;
@@ -67,17 +66,30 @@ internal class HeuristicFieldRewriter : BaseInstructionHandler
/// <param name="fieldRef">The field reference.</param>
/// <param name="declaringType">The type on which the field was defined.</param>
/// <param name="isRead">Whether the field is being read; else it's being written to.</param>
private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead)
private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition? declaringType, bool isRead)
{
// get equivalent property
PropertyDefinition? property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name);
MethodDefinition? method = isRead ? property?.GetMethod : property?.SetMethod;
if (method == null)
MethodDefinition? accessor = null;
if (declaringType is not null)
{
PropertyDefinition? targetProperty = null;
foreach (PropertyDefinition property in declaringType.Properties)
{
if (property.Name == fieldRef.Name)
{
targetProperty = property;
break;
}
}
accessor = isRead ? targetProperty?.GetMethod : targetProperty?.SetMethod;
}
if (accessor is null)
return false;
// rewrite field to property
instruction.OpCode = OpCodes.Call;
instruction.Operand = module.ImportReference(method);
instruction.Operand = module.ImportReference(accessor);
this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)");
return this.MarkRewritten();
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Rewriters;
@@ -46,6 +46,7 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler
return false;
// get method definition
int firstMissingIndex = methodRef.Parameters.Count;
MethodDefinition? method = null;
foreach (MethodDefinition match in type.Methods)
{
@@ -53,11 +54,11 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler
continue;
// reference matches initial parameters of definition
if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match))
if (firstMissingIndex >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match))
continue;
// all remaining parameters in definition are optional
if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional))
if (!this.RemainingParametersAreOptional(match.Parameters, startAt: firstMissingIndex))
continue;
method = match;
@@ -67,15 +68,14 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler
return false;
// get instructions to inject parameter values
var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count)
.Select(p => RewriteHelper.GetLoadValueInstruction(p.Constant))
.ToArray();
// skip if invalid
foreach (Instruction? loadInstruction in loadInstructions)
Instruction[] loadInstructions = new Instruction[method.Parameters.Count - firstMissingIndex];
for (int i = 0; i + firstMissingIndex < method.Parameters.Count; i++)
{
Instruction? loadInstruction = RewriteHelper.GetLoadValueInstruction(method.Parameters[i + firstMissingIndex].Constant);
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
loadInstructions[i] = loadInstruction;
}
// rewrite method reference
@@ -114,4 +114,18 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler
return true;
}
/// <summary>Get whether all parameters starting from the given index are marked optional.</summary>
/// <param name="parameters">The parameters to check.</param>
/// <param name="startAt">The index of the first parameter to check.</param>
private bool RemainingParametersAreOptional(Collection<ParameterDefinition> parameters, int startAt)
{
for (int i = startAt; i < parameters.Count; i++)
{
if (!parameters[i].IsOptional)
return false;
}
return true;
}
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
namespace StardewModdingAPI.Framework.ModLoading;
@@ -73,12 +72,12 @@ internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
typeNameB = this.MapPlaceholder(placeholder: typeNameB, type: typeNameA, map: tokenMap);
// compare inner tokens
string[] symbolsA = this.GetTypeSymbols(typeNameA).ToArray();
string[] symbolsB = this.GetTypeSymbols(typeNameB).ToArray();
if (symbolsA.Length != symbolsB.Length)
IReadOnlyList<string> symbolsA = this.GetTypeSymbols(typeNameA);
IReadOnlyList<string> symbolsB = this.GetTypeSymbols(typeNameB);
if (symbolsA.Count != symbolsB.Count)
return false;
for (int i = 0; i < symbolsA.Length; i++)
for (int i = 0; i < symbolsA.Count; i++)
{
if (!HeuristicallyEqualsImpl(symbolsA[i], symbolsB[i], tokenMap))
return false;
@@ -106,10 +105,11 @@ internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
/// <summary>Get the top-level type symbols in a type name (e.g. <code>List</code> and <code>NetRef&lt;T&gt;</code> in <code>List&lt;NetRef&lt;T&gt;&gt;</code>)</summary>
/// <param name="typeName">The full type name.</param>
private IEnumerable<string> GetTypeSymbols(string typeName)
private IReadOnlyList<string> GetTypeSymbols(string typeName)
{
int openGenerics = 0;
List<string> symbols = [];
int openGenerics = 0;
Queue<char> queue = new(typeName);
string symbol = "";
while (queue.Count > 0)
@@ -129,7 +129,7 @@ internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
{
// start new generic symbol
case 0:
yield return symbol;
symbols.Add(symbol);
symbol = "";
openGenerics++;
break;
@@ -152,7 +152,7 @@ internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
// start next generic symbol
case 1:
yield return symbol;
symbols.Add(symbol);
symbol = "";
break;
@@ -174,7 +174,7 @@ internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
// end generic symbol
case 1:
yield return symbol;
symbols.Add(symbol);
symbol = "";
openGenerics--;
break;
@@ -195,6 +195,8 @@ internal class TypeReferenceComparer : IEqualityComparer<TypeReference?>
}
if (symbol != "")
yield return symbol;
symbols.Add(symbol);
return symbols;
}
}
+1 -1
View File
@@ -883,7 +883,7 @@ internal class SCore : IDisposable
string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.GetLocale()}.";
if (Context.IsMultiplayer)
{
int onlineCount = Game1.getOnlineFarmers().Count();
int onlineCount = Game1.getOnlineFarmers().Count;
context += $" {(Context.IsMainPlayer ? "Main player" : "Farmhand")} with {onlineCount} {(onlineCount == 1 ? "player" : "players")} online.";
}
else
+7 -4
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Enums;
@@ -145,9 +144,13 @@ internal class SGameRunner : GameRunner
/// <param name="playerId">The player ID to check.</param>
public int? GetScreenId(long playerId)
{
return this.gameInstances
.FirstOrDefault(p => ((SGame)p).PlayerId == playerId)
?.instanceId;
foreach (Game1 gameInstance in this.gameInstances)
{
if (((SGame)gameInstance).PlayerId == playerId)
return gameInstance.instanceId;
}
return null;
}
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers;
@@ -71,8 +70,16 @@ internal class ComparableListWatcher<TValue> : BaseDisposableWatcher, ICollectio
// detect changes
HashSet<TValue> curValues = new HashSet<TValue>(this.CurrentValues, this.LastValues.Comparer);
this.RemovedImpl.AddRange(from value in this.LastValues where !curValues.Contains(value) select value);
this.AddedImpl.AddRange(from value in curValues where !this.LastValues.Contains(value) select value);
foreach (TValue value in this.LastValues)
{
if (!curValues.Contains(value))
this.RemovedImpl.Add(value);
}
foreach (TValue value in curValues)
{
if (!this.LastValues.Contains(value))
this.AddedImpl.Add(value);
}
this.LastValues = curValues;
}
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers;
@@ -86,25 +85,31 @@ internal class ObservableCollectionWatcher<TValue> : BaseDisposableWatcher, ICol
/// <param name="e">The event arguments.</param>
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// clear
if (e.Action == NotifyCollectionChangedAction.Reset)
{
this.RemovedImpl.AddRange(this.PreviousValues);
this.PreviousValues.Clear();
return;
}
else
{
TValue[]? added = e.NewItems?.Cast<TValue>().ToArray();
TValue[]? removed = e.OldItems?.Cast<TValue>().ToArray();
if (removed != null)
// removed items
if (e.OldItems != null)
{
foreach (TValue value in e.OldItems)
this.RemovedImpl.Add(value);
this.PreviousValues.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
}
// added items
if (e.NewItems != null)
{
int insertAt = e.NewStartingIndex;
foreach (TValue value in e.NewItems)
{
this.RemovedImpl.AddRange(removed);
this.PreviousValues.RemoveRange(e.OldStartingIndex, removed.Length);
}
if (added != null)
{
this.AddedImpl.AddRange(added);
this.PreviousValues.InsertRange(e.NewStartingIndex, added);
this.AddedImpl.Add(value);
this.PreviousValues.Insert(insertAt, value);
insertAt++;
}
}
}
@@ -28,8 +28,7 @@ internal class PlayerSnapshot
/// <summary>Tracks changes to the player's skill levels.</summary>
public IDictionary<SkillType, SnapshotDiff<int>> Skills { get; } =
Enum
.GetValues(typeof(SkillType))
.Cast<SkillType>()
.GetValues<SkillType>()
.ToDictionary(skill => skill, _ => new SnapshotDiff<int>());
/// <summary>Get a list of inventory changes.</summary>
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -163,7 +162,7 @@ internal class JsonSettingsWrapper
{
oldSet = new HashSet<string>(oldSet, StringComparer.OrdinalIgnoreCase);
bool changed = newValue.Count != oldSet.Count || !oldSet.All(newValue.Contains);
bool changed = newValue.Count != oldSet.Count || !oldSet.IsSubsetOf(newValue);
if (changed)
{
this.Properties[fieldName] = JToken.FromObject(newValue);
+7 -4
View File
@@ -754,8 +754,11 @@ internal class CoreAssetPropagator
{
List<NPC> characters = [];
foreach (NPC character in this.GetLocations().SelectMany(p => p.characters))
characters.Add(character);
foreach (GameLocation location in this.GetLocations())
{
foreach (NPC character in location.characters)
characters.Add(character);
}
if (Game1.CurrentEvent?.actors != null)
{
@@ -823,9 +826,9 @@ internal class CoreAssetPropagator
// get child locations
if (buildingInteriors)
{
foreach (GameLocation location in locations.Select(p => p.Location).ToArray())
foreach (LocationInfo location in locations.ToArray())
{
foreach (Building building in location.buildings)
foreach (Building building in location.Location.buildings)
{
GameLocation indoors = building.indoors.Value;
if (indoors is not null)
-1
View File
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using StardewModdingAPI.Framework;
+8 -2
View File
@@ -152,7 +152,13 @@ public class KeybindList
/// <summary>Get the keybind which is currently down, if any. If there are multiple keybinds down, the first one is returned.</summary>
public Keybind? GetKeybindCurrentlyDown()
{
return this.Keybinds.FirstOrDefault(p => p.GetState().IsDown());
foreach (Keybind keybind in this.Keybinds)
{
if (keybind.GetState().IsDown())
return keybind;
}
return null;
}
/// <summary>Get a string representation of the input binding.</summary>
@@ -161,6 +167,6 @@ public class KeybindList
{
return this.Keybinds.Length > 0
? string.Join(", ", this.Keybinds.Select(p => p.ToString()))
: SButton.None.ToString();
: nameof(SButton.None);
}
}