Added gas grid optimization, various despawn optimizations, new hauling optimizations utilizing events and regions, bugfixes for old patches and for vanilla functions too, as well as smaller additions. 1.3 files removed.

This commit is contained in:
bbradson
2024-01-27 10:24:24 +01:00
parent 0247ad59a1
commit c8b764de68
151 changed files with 7318 additions and 2145 deletions
@@ -4,14 +4,15 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
namespace PerformanceFish.System;
public class EqualityComparerOptimization : ClassWithFishPatches
public sealed class EqualityComparerOptimization : ClassWithFishPatches
{
public class Optimization : FishPatch
public sealed class Optimization : FishPatch
{
public override string? Description { get; }
= "Specialized EqualityComparers for slightly faster collection lookups on ValueTypes. Can reduce memory "
@@ -74,7 +75,7 @@ public class EqualityComparerOptimization : ClassWithFishPatches
}
[Serializable]
public class EquatableValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct, IEquatable<T>
public sealed class EquatableValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct, IEquatable<T>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(T x, T y) => x.Equals(y);
@@ -85,71 +86,73 @@ public class EquatableValueTypeEqualityComparer<T> : EqualityComparer<T> where T
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is EquatableValueTypeEqualityComparer<T>;
public override bool Equals(object? obj) => obj is EquatableValueTypeEqualityComparer<T>;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class EquatableNullableEqualityComparer<T> : EqualityComparer<T?> where T : struct, IEquatable<T>
public sealed class EquatableNullableEqualityComparer<T> : EqualityComparer<T?> where T : struct, IEquatable<T>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(T? x, T? y)
=> x.HasValue
? y.HasValue && x.Value.Equals(y.Value)
? y.HasValue && x.GetValueOrDefault().Equals(y.GetValueOrDefault())
: !y.HasValue;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode(T? obj) => !obj.HasValue ? 0 : HashCode.Get(obj);
public override int GetHashCode(T? obj) => !obj.HasValue ? 0 : HashCode.Get(obj.GetValueOrDefault());
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is EquatableNullableEqualityComparer<T>;
public override bool Equals(object? obj) => obj is EquatableNullableEqualityComparer<T>;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class NullableEqualityComparer<T> : EqualityComparer<T?> where T : struct
public sealed class NullableEqualityComparer<T> : EqualityComparer<T?> where T : struct
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(T? x, T? y)
=> x.HasValue
? y.HasValue && x.Value.Equals<T>(y.Value)
? y.HasValue && x.GetValueOrDefault().Equals<T>(y.GetValueOrDefault())
: !y.HasValue;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode(T? obj) => !obj.HasValue ? 0 : HashCode.Get(obj);
public override int GetHashCode(T? obj) => !obj.HasValue ? 0 : HashCode.Get(obj.GetValueOrDefault());
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is NullableEqualityComparer<T>;
public override bool Equals(object? obj) => obj is NullableEqualityComparer<T>;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class StringEqualityComparer : EqualityComparer<string>
public sealed class StringEqualityComparer : EqualityComparer<string?>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(string x, string y)
[SuppressMessage("Globalization", "CA1307")]
[SuppressMessage("Globalization", "CA1309")]
public override bool Equals(string? x, string? y)
=> string.Equals(x, y); // already does a null check, so this skips the extra check that'd otherwise happen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode(string obj) => obj.GetHashCode();
public override int GetHashCode(string? obj) => obj?.GetHashCode() ?? 0;
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is StringEqualityComparer;
public override bool Equals(object? obj) => obj is StringEqualityComparer;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class IntEqualityComparer : EqualityComparer<int>
public sealed class IntEqualityComparer : EqualityComparer<int>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(int x, int y) => x == y;
@@ -160,13 +163,13 @@ public class IntEqualityComparer : EqualityComparer<int>
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is IntEqualityComparer;
public override bool Equals(object? obj) => obj is IntEqualityComparer;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class UshortEqualityComparer : EqualityComparer<ushort>
public sealed class UshortEqualityComparer : EqualityComparer<ushort>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(ushort x, ushort y) => x == y;
@@ -177,13 +180,13 @@ public class UshortEqualityComparer : EqualityComparer<ushort>
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is UshortEqualityComparer;
public override bool Equals(object? obj) => obj is UshortEqualityComparer;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class ReferenceEqualityComparer<T> : EqualityComparer<T>
public sealed class ReferenceEqualityComparer<T> : EqualityComparer<T>
{
public new static readonly ReferenceEqualityComparer<T> Default = new();
@@ -196,13 +199,13 @@ public class ReferenceEqualityComparer<T> : EqualityComparer<T>
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is ReferenceEqualityComparer<T>;
public override bool Equals(object? obj) => obj is ReferenceEqualityComparer<T>;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
[Serializable]
public class ValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct
public sealed class ValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct
{
public new static readonly ValueTypeEqualityComparer<T> Default = new();
@@ -215,7 +218,7 @@ public class ValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct
/// <summary>
/// Equals method for the comparer itself.
/// </summary>
public override bool Equals(object obj) => obj is ReferenceEqualityComparer<T>;
public override bool Equals(object? obj) => obj is ReferenceEqualityComparer<T>;
public override int GetHashCode() => GetType().Name.GetHashCode();
}
@@ -3,13 +3,13 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading.Tasks;
using PerformanceFish.Cache;
using PerformanceFish.ModCompatibility;
using ConstructorInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
@@ -49,14 +49,14 @@ using TypeFullNameCache
using ActivatorCache
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeTypeHandle,
PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>;
using ActivatorWithArgumentsCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>;
// using ActivatorWithArgumentsCache
// = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle,
// PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
// PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>;
namespace PerformanceFish.System;
public class ReflectionCaching : ClassWithFishPatches
public sealed class ReflectionCaching : ClassWithFishPatches
{
static ReflectionCaching() // necessary to prevent recursion between patches and function pointer creation
{
@@ -85,15 +85,15 @@ public class ReflectionCaching : ClassWithFishPatches
public BindingFlags Flags;
}
public class TypePatches
public sealed class TypePatches
{
public class GetField_Patch : FishPatch
public sealed class GetField_Patch : FishPatch
{
public override string Description { get; } = "Caches GetField lookups";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(RuntimeType), nameof(RuntimeType.GetField),
new[] { typeof(string), typeof(BindingFlags) })!;
[typeof(string), typeof(BindingFlags)])!;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, out FieldInfo? __result,
@@ -132,11 +132,12 @@ public class ReflectionCaching : ClassWithFishPatches
= __result;
}
public class GetMethodWithFlags_Patch : FishPatch
public sealed class GetMethodWithFlags_Patch : FishPatch
{
public override string Description { get; } = "Caches GetMethod lookups";
public override Expression<Action> TargetMethod { get; }
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
= static () => default(Type)!.GetMethod(null!, default(BindingFlags));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -168,11 +169,12 @@ public class ReflectionCaching : ClassWithFishPatches
= __result;
}
public class GetMethodWithTypes_Patch : FishPatch
public sealed class GetMethodWithTypes_Patch : FishPatch
{
public override string Description { get; } = "Caches GetMethod lookups";
public override Expression<Action> TargetMethod { get; }
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
= static () => default(Type)!.GetMethod(null!, null!);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -201,7 +203,7 @@ public class ReflectionCaching : ClassWithFishPatches
= __result;
}
public class GetMethodWithFlagsAndTypes_Patch : FishPatch
public sealed class GetMethodWithFlagsAndTypes_Patch : FishPatch
{
public override string Description { get; } = "Caches GetMethod lookups";
@@ -244,7 +246,7 @@ public class ReflectionCaching : ClassWithFishPatches
new(types)).Info = __result;
}
public class GetConstructor_Patch : FishPatch
public sealed class GetConstructor_Patch : FishPatch
{
public override string Description { get; } = "Caches GetConstructor lookups";
@@ -285,11 +287,12 @@ public class ReflectionCaching : ClassWithFishPatches
= __result;
}
public class GetProperty_Patch : FishPatch
public sealed class GetProperty_Patch : FishPatch
{
public override string Description { get; } = "Caches GetProperty lookups";
public override Expression<Action> TargetMethod { get; }
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
= static () => default(Type)!.GetProperty(null!, default(BindingFlags));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -322,7 +325,7 @@ public class ReflectionCaching : ClassWithFishPatches
= __result;
}
public class GetFullName : FishPatch
public sealed class GetFullName : FishPatch
{
public override string? Description { get; }
= "Normally averages at over 1000ns per call. Caching makes this about 30 times faster, including "
@@ -354,7 +357,6 @@ public class ReflectionCaching : ClassWithFishPatches
ref var centralCache
= ref TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
cache.Name = centralCache.Name;
// cache.Cached = centralCache.Cached;
}
return cache.Cached;
@@ -374,14 +376,12 @@ public class ReflectionCaching : ClassWithFishPatches
{
ref var cache = ref TypeFullNameCache.GetExistingReference(__instance.TypeHandle);
cache.Name = __result;
// cache.Cached = 1;
lock (_lock)
{
ref var centralCache
= ref TypeFullNameCache.GetDirectly.GetReference(__instance.TypeHandle);
centralCache.Name = __result;
// centralCache.Cached = 1;
}
}
@@ -390,6 +390,7 @@ public class ReflectionCaching : ClassWithFishPatches
// probably never actually needed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("Globalization", "CA1308")]
private static string ToLowerIfNeededForBindingFlags(string name, BindingFlags bindingAttr)
=> (bindingAttr & BindingFlags.IgnoreCase) != 0 ? name.ToLowerInvariant() : name;
@@ -436,9 +437,9 @@ public class ReflectionCaching : ClassWithFishPatches
}
}
public class MonoCustomAttrs
public sealed class MonoCustomAttrs
{
public class GetCustomAttributes : FishPatch
public sealed class GetCustomAttributes : FishPatch
{
public override string? Description { get; }
= "Caches attributes for reflection lookups. Decent load time improvement.";
@@ -544,18 +545,24 @@ public class ReflectionCaching : ClassWithFishPatches
=> _hashCode;
}
public class ActivatorPatches
public sealed class ActivatorPatches
{
public class CreateInstance_Type : FishPatch
public sealed class CreateInstance_Type : FishPatch
{
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
public override string? Description { get; }
= "Optimizes the parameterless Activator.CreateInstance method by invoking through specialized cached "
+ "delegates";
public override Delegate? TargetMethodGroup { get; } = (Func<Type, object>)Activator.CreateInstance;
public static bool Prefix(Type type, ref object __result)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type? type, ref object __result)
{
if (type is null)
return true;
ref var cache = ref ActivatorCache.GetOrAddReference(type.TypeHandle);
if (cache.Dirty
@@ -570,7 +577,7 @@ public class ReflectionCaching : ClassWithFishPatches
}
}
// public class CreateInstance_Type_Args : FishPatch
// public sealed class CreateInstance_Type_Args : FishPatch
// {
// public override Delegate? TargetMethodGroup { get; }
// = (Func<Type, object[], object>)Activator.CreateInstance;
@@ -669,9 +676,9 @@ public class ReflectionCaching : ClassWithFishPatches
// }
// }
public class FieldInfoPatches
public sealed class FieldInfoPatches
{
public class GetValue_Patch : FishPatch
public sealed class GetValue_Patch : FishPatch
{
public override string Description { get; }
= "Optimizes the GetValue method by invoking it through specialized cached delegates";
@@ -705,7 +712,7 @@ public class ReflectionCaching : ClassWithFishPatches
}
}
public class SetValue_Patch : FishPatch
public sealed class SetValue_Patch : FishPatch
{
public override string Description { get; }
= "Optimizes the SetValue method by invoking it through specialized cached delegates";
@@ -718,29 +725,45 @@ public class ReflectionCaching : ClassWithFishPatches
{
ref var cache = ref FieldSetters.GetOrAddReference(__instance.FieldHandle);
if (cache.Dirty
&& !FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
__instance.FieldHandle, __instance))
{
if (cache.Dirty && !TryUpdate(__instance, ref cache))
return true;
}
cache.Result!(obj, value);
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryUpdate(FieldInfo __instance, ref FieldInfoSetterCache cache)
=> cache.Task != _fieldSetterFailureTask && __instance.IsConst()
? ErrorForInvalidCallAttempt(__instance, ref cache)
: FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
__instance.FieldHandle, __instance);
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool ErrorForInvalidCallAttempt(FieldInfo info, ref FieldInfoSetterCache cache)
{
cache.Task = _fieldSetterFailureTask;
Log.Error($"FieldInfo.SetValue has been called on '{
info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at "
+ $"setting a value will always fail.");
return false;
}
}
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!);
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
{
try
{
var dm = new DynamicMethod($"FieldGetter_{info.Name}", typeof(object), new[] { typeof(object) },
var dm = new DynamicMethod($"FieldGetter_{info.Name}", typeof(object), [typeof(object)],
GetOwnerType(info), true);
var il = dm.GetILGenerator();
TryEmitFieldInstanceArgument(il, info);
il.Emit(info is { IsLiteral: true, IsInitOnly: false }
il.Emit(info.IsConst()
? FishTranspiler.Constant(info.GetRawConstantValue())
: FishTranspiler.Field(info));
@@ -755,7 +778,7 @@ public class ReflectionCaching : ClassWithFishPatches
{
Log.Error($"PerformanceFish failed to generate an optimized delegate for {
info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
Environment.StackTrace}");
return null;
}
}
@@ -765,7 +788,7 @@ public class ReflectionCaching : ClassWithFishPatches
try
{
var dm = new DynamicMethod($"FieldSetter_{info.Name}", typeof(void),
new[] { typeof(object), typeof(object) }, GetOwnerType(info), true);
[typeof(object), typeof(object)], GetOwnerType(info), true);
var il = dm.GetILGenerator();
TryEmitFieldInstanceArgument(il, info);
@@ -776,18 +799,17 @@ public class ReflectionCaching : ClassWithFishPatches
{
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching),
info.FieldType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
new[] { typeof(object) },
new[]
{
[typeof(object)],
[
info.FieldType.IsNullable() // TODO: verify that this is actually correct
? Nullable.GetUnderlyingType(info.FieldType)
: info.FieldType
}));
]));
}
else
{
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert),
generics: new[] { info.FieldType }));
generics: [info.FieldType]));
}
il.Emit(FishTranspiler.StoreField(info));
@@ -800,7 +822,7 @@ public class ReflectionCaching : ClassWithFishPatches
{
Log.Error($"PerformanceFish failed to generate an optimized delegate for {
info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
Environment.StackTrace}");
return null;
}
}
@@ -839,9 +861,9 @@ public class ReflectionCaching : ClassWithFishPatches
public delegate void FieldSetter(object? obj, object? value);
}
public class MethodBasePatches
public sealed class MethodBasePatches
{
public class Invoke_Patch : FishPatch
public sealed class Invoke_Patch : FishPatch
{
public override bool DefaultState => false; // TODO: fix
@@ -885,6 +907,7 @@ public class ReflectionCaching : ClassWithFishPatches
}
// Similar to HarmonyLib.MethodInvoker, but correctly handles a couple of edge cases Harmony would throw on.
[SuppressMessage("Globalization", "CA1310")]
public static MethodInvoker? MakeInvokeDelegate(MethodBase methodBase)
{
if (methodBase.DeclaringType is null && methodBase.Name.StartsWith("PadMethod"))
@@ -902,7 +925,7 @@ public class ReflectionCaching : ClassWithFishPatches
return null;
var dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
new[] { typeof(object), typeof(object[]) },
[typeof(object), typeof(object[])],
GetOwnerType(methodBase), true);
var il = dm.GetILGenerator();
@@ -937,7 +960,7 @@ public class ReflectionCaching : ClassWithFishPatches
{
Log.Warning($"PerformanceFish failed to generate an optimized delegate for {
Reflection.FullDescription(methodBase)}. Reverting to default behaviour instead.\n{ex}\n{
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
Environment.StackTrace}");
return null;
}
}
@@ -961,15 +984,14 @@ public class ReflectionCaching : ClassWithFishPatches
il.Emit(parameterType.IsValueType
? FishTranspiler.Call(typeof(ReflectionCaching),
parameterType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
new[] { typeof(object) },
new[]
{
[typeof(object)],
[
parameterType.IsNullable()
? Nullable.GetUnderlyingType(parameterType)
: parameterType
})
])
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert),
generics: new[] { parameterType }));
generics: [parameterType]));
}
private static void EmitInvokeByRefParameterLoad(ILGenerator il, Type parameterType)
@@ -981,7 +1003,7 @@ public class ReflectionCaching : ClassWithFishPatches
if (parameterType.IsNullable())
{
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(VerifyNullableBoxObject),
generics: new[] { Nullable.GetUnderlyingType(parameterType) }));
generics: [Nullable.GetUnderlyingType(parameterType)]));
il.Emit(FishTranspiler.UnboxAddress(parameterType));
}
@@ -989,16 +1011,13 @@ public class ReflectionCaching : ClassWithFishPatches
{
il.Emit(parameterType.IsValueType
? FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
// new[] { typeof(object).MakeByRefType() },
generics: new[]
{
generics:
[
/*parameterType.IsNullable()
? Nullable.GetUnderlyingType(parameterType)
:*/ parameterType
})
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely),
// new[] { typeof(object).MakeByRefType() },
generics: new[] { parameterType }));
])
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely), generics: [parameterType]));
}
}
@@ -1022,14 +1041,12 @@ public class ReflectionCaching : ClassWithFishPatches
if (declaringType.IsValueType)
{
il.Emit(FishTranspiler.ArgumentAddress(0));
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
generics: new[] { declaringType }));
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely), generics: [declaringType]));
}
else
{
il.Emit(FishTranspiler.This);
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert),
generics: new[] { declaringType }));
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert), generics: [declaringType]));
}
}