1.6 to main #1
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,353 @@
|
||||
// Copyright (c) 2026 Bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// 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 Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public sealed class CompTickOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
private static readonly MethodInfo _thingCompTickMethod
|
||||
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTick));
|
||||
|
||||
private static readonly MethodInfo _thingCompTickIntervalMethod
|
||||
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTickInterval), [typeof(int)]);
|
||||
|
||||
private static readonly MethodInfo _thingCompTickRareMethod
|
||||
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTickRare));
|
||||
|
||||
private static readonly MethodInfo _thingCompTickLongMethod
|
||||
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTickLong));
|
||||
|
||||
private static readonly MethodInfo _hediffCompPostTickMethod
|
||||
= AccessTools.DeclaredMethod(typeof(HediffComp), nameof(HediffComp.CompPostTick), [typeof(float).MakeByRefType()]);
|
||||
|
||||
private static readonly MethodInfo _hediffCompPostTickIntervalMethod = AccessTools.DeclaredMethod(
|
||||
typeof(HediffComp),
|
||||
nameof(HediffComp.CompPostTickInterval),
|
||||
[typeof(float).MakeByRefType(), typeof(int)]);
|
||||
|
||||
private static readonly bool _thingCompTickBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickMethod);
|
||||
private static readonly bool _thingCompTickIntervalBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickIntervalMethod);
|
||||
private static readonly bool _thingCompTickRareBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickRareMethod);
|
||||
private static readonly bool _thingCompTickLongBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickLongMethod);
|
||||
private static readonly bool _hediffCompPostTickBaseIsTrivial = IsTrivialVoidMethod(_hediffCompPostTickMethod);
|
||||
private static readonly bool _hediffCompPostTickIntervalBaseIsTrivial
|
||||
= IsTrivialVoidMethod(_hediffCompPostTickIntervalMethod);
|
||||
|
||||
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTick
|
||||
= new(static () => _thingCompTickBaseIsTrivial && !HasHarmonyPatches(_thingCompTickMethod));
|
||||
|
||||
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickInterval
|
||||
= new(static () => _thingCompTickIntervalBaseIsTrivial && !HasHarmonyPatches(_thingCompTickIntervalMethod));
|
||||
|
||||
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickRare
|
||||
= new(static () => _thingCompTickRareBaseIsTrivial && !HasHarmonyPatches(_thingCompTickRareMethod));
|
||||
|
||||
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickLong
|
||||
= new(static () => _thingCompTickLongBaseIsTrivial && !HasHarmonyPatches(_thingCompTickLongMethod));
|
||||
|
||||
private static readonly Lazy<bool> _canSkipInheritedBaseHediffCompPostTick
|
||||
= new(static () => _hediffCompPostTickBaseIsTrivial && !HasHarmonyPatches(_hediffCompPostTickMethod));
|
||||
|
||||
private static readonly Lazy<bool> _canSkipInheritedBaseHediffCompPostTickInterval = new(
|
||||
static () => _hediffCompPostTickIntervalBaseIsTrivial && !HasHarmonyPatches(_hediffCompPostTickIntervalMethod));
|
||||
|
||||
private static readonly FishTable<Type, bool> _thingCompTickTypes = new()
|
||||
{
|
||||
ValueInitializer = static type => InheritsThingCompTick(type)
|
||||
};
|
||||
|
||||
private static readonly FishTable<Type, bool> _thingCompTickIntervalTypes = new()
|
||||
{
|
||||
ValueInitializer = static type => InheritsThingCompTickInterval(type)
|
||||
};
|
||||
|
||||
private static readonly FishTable<Type, bool> _thingCompTickRareTypes = new()
|
||||
{
|
||||
ValueInitializer = static type => InheritsThingCompTickRare(type)
|
||||
};
|
||||
|
||||
private static readonly FishTable<Type, bool> _thingCompTickLongTypes = new()
|
||||
{
|
||||
ValueInitializer = static type => InheritsThingCompTickLong(type)
|
||||
};
|
||||
|
||||
private static readonly FishTable<Type, bool> _hediffCompPostTickTypes = new()
|
||||
{
|
||||
ValueInitializer = static type => InheritsHediffCompPostTick(type)
|
||||
};
|
||||
|
||||
private static readonly FishTable<Type, bool> _hediffCompPostTickIntervalTypes = new()
|
||||
{
|
||||
ValueInitializer = static type => InheritsHediffCompPostTickInterval(type)
|
||||
};
|
||||
|
||||
public sealed class ThingTickPatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Skips inherited empty ThingComp.CompTick calls when the base method is still unpatched and trivial.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(ThingWithComps), "Tick");
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(ThingWithComps instance)
|
||||
{
|
||||
var comps = instance.AllComps;
|
||||
var count = comps.Count;
|
||||
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTick();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (!canSkipInheritedBase || !_thingCompTickTypes.GetOrAdd(comp.GetType()))
|
||||
comp.CompTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ThingTickIntervalPatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Skips inherited empty ThingComp.CompTickInterval calls when the base method is still unpatched and trivial.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(ThingWithComps), "TickInterval", [typeof(int)]);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(ThingWithComps instance, int delta)
|
||||
{
|
||||
var comps = instance.AllComps;
|
||||
var count = comps.Count;
|
||||
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickInterval();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (!canSkipInheritedBase || !_thingCompTickIntervalTypes.GetOrAdd(comp.GetType()))
|
||||
comp.CompTickInterval(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ThingTickRarePatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Skips inherited empty ThingComp.CompTickRare calls when the base method is still unpatched and trivial.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(ThingWithComps), nameof(ThingWithComps.TickRare));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(ThingWithComps instance)
|
||||
{
|
||||
var comps = instance.AllComps;
|
||||
var count = comps.Count;
|
||||
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickRare();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (!canSkipInheritedBase || !_thingCompTickRareTypes.GetOrAdd(comp.GetType()))
|
||||
comp.CompTickRare();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ThingTickLongPatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Skips inherited empty ThingComp.CompTickLong calls when the base method is still unpatched and trivial.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(ThingWithComps), nameof(ThingWithComps.TickLong));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(ThingWithComps instance)
|
||||
{
|
||||
var comps = instance.AllComps;
|
||||
var count = comps.Count;
|
||||
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickLong();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (!canSkipInheritedBase || !_thingCompTickLongTypes.GetOrAdd(comp.GetType()))
|
||||
comp.CompTickLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HediffPostTickPatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Skips inherited empty HediffComp.CompPostTick calls when the base method is still unpatched and trivial.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(HediffWithComps), nameof(HediffWithComps.PostTick));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(HediffWithComps instance)
|
||||
{
|
||||
var comps = instance.comps;
|
||||
if (comps == null)
|
||||
return;
|
||||
|
||||
var count = comps.Count;
|
||||
var severityAdjustment = 0f;
|
||||
var canSkipInheritedBase = CanSkipInheritedBaseHediffCompPostTick();
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (!canSkipInheritedBase || !_hediffCompPostTickTypes.GetOrAdd(comp.GetType()))
|
||||
comp.CompPostTick(ref severityAdjustment);
|
||||
}
|
||||
|
||||
if (severityAdjustment != 0f)
|
||||
instance.Severity += severityAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HediffPostTickIntervalPatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Skips inherited empty HediffComp.CompPostTickInterval calls when the base method is still unpatched and trivial.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(HediffWithComps), nameof(HediffWithComps.PostTickInterval), [typeof(int)]);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(HediffWithComps instance, int delta)
|
||||
{
|
||||
var comps = instance.comps;
|
||||
if (comps == null)
|
||||
return;
|
||||
|
||||
var count = comps.Count;
|
||||
var severityAdjustment = 0f;
|
||||
var canSkipInheritedBase = CanSkipInheritedBaseHediffCompPostTickInterval();
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (!canSkipInheritedBase || !_hediffCompPostTickIntervalTypes.GetOrAdd(comp.GetType()))
|
||||
comp.CompPostTickInterval(ref severityAdjustment, delta);
|
||||
}
|
||||
|
||||
if (severityAdjustment != 0f)
|
||||
instance.Severity += severityAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSkipInheritedBaseThingCompTick()
|
||||
=> _canSkipInheritedBaseThingCompTick.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSkipInheritedBaseThingCompTickInterval()
|
||||
=> _canSkipInheritedBaseThingCompTickInterval.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSkipInheritedBaseThingCompTickRare()
|
||||
=> _canSkipInheritedBaseThingCompTickRare.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSkipInheritedBaseThingCompTickLong()
|
||||
=> _canSkipInheritedBaseThingCompTickLong.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSkipInheritedBaseHediffCompPostTick()
|
||||
=> _canSkipInheritedBaseHediffCompPostTick.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanSkipInheritedBaseHediffCompPostTickInterval()
|
||||
=> _canSkipInheritedBaseHediffCompPostTickInterval.Value;
|
||||
|
||||
private static bool InheritsThingCompTick(Type type)
|
||||
=> AccessTools.Method(type, nameof(ThingComp.CompTick))?.DeclaringType == typeof(ThingComp);
|
||||
|
||||
private static bool InheritsThingCompTickInterval(Type type)
|
||||
=> AccessTools.Method(type, nameof(ThingComp.CompTickInterval), [typeof(int)])?.DeclaringType == typeof(ThingComp);
|
||||
|
||||
private static bool InheritsThingCompTickRare(Type type)
|
||||
=> AccessTools.Method(type, nameof(ThingComp.CompTickRare))?.DeclaringType == typeof(ThingComp);
|
||||
|
||||
private static bool InheritsThingCompTickLong(Type type)
|
||||
=> AccessTools.Method(type, nameof(ThingComp.CompTickLong))?.DeclaringType == typeof(ThingComp);
|
||||
|
||||
private static bool InheritsHediffCompPostTick(Type type)
|
||||
=> AccessTools.Method(type, nameof(HediffComp.CompPostTick), [typeof(float).MakeByRefType()])?.DeclaringType
|
||||
== typeof(HediffComp);
|
||||
|
||||
private static bool InheritsHediffCompPostTickInterval(Type type)
|
||||
=> AccessTools.Method(type, nameof(HediffComp.CompPostTickInterval),
|
||||
[typeof(float).MakeByRefType(), typeof(int)])?.DeclaringType
|
||||
== typeof(HediffComp);
|
||||
|
||||
private static bool IsTrivialVoidMethod(MethodInfo method)
|
||||
{
|
||||
if (method.ReturnType != typeof(void)
|
||||
|| method.GetMethodBody() is not { ExceptionHandlingClauses.Count: 0, LocalVariables.Count: 0 } body)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var il = body.GetILAsByteArray();
|
||||
if (il == null || il.Length == 0)
|
||||
return false;
|
||||
|
||||
var sawRet = false;
|
||||
|
||||
for (var i = 0; i < il.Length; i++)
|
||||
{
|
||||
switch (il[i])
|
||||
{
|
||||
case 0x00:
|
||||
continue;
|
||||
case 0x2A when i == il.Length - 1 && !sawRet:
|
||||
sawRet = true;
|
||||
continue;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return sawRet;
|
||||
}
|
||||
|
||||
private static bool HasHarmonyPatches(MethodBase method)
|
||||
{
|
||||
var patches = Harmony.GetPatchInfo(method);
|
||||
return patches is not null
|
||||
&& (patches.Prefixes.Count != 0
|
||||
|| patches.Postfixes.Count != 0
|
||||
|| patches.Transpilers.Count != 0
|
||||
|| patches.Finalizers.Count != 0);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
if (SavedTick != TickHelper.TicksGame)
|
||||
if (SavedDayTickTick != TickHelper.TicksGame)
|
||||
return __state = true;
|
||||
|
||||
__result = DayTick;
|
||||
@@ -64,12 +64,88 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
|
||||
if (!__state || SavedMap != map)
|
||||
return;
|
||||
|
||||
SavedTick = TickHelper.TicksGame;
|
||||
SavedDayTickTick = TickHelper.TicksGame;
|
||||
DayTick = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HourIntegerByMap_Patch : FishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Caches results of GenLocalDate.HourInteger for the first map.";
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = (Func<Map, int>)GenLocalDate.HourInteger;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Map map, ref int __result, out bool __state)
|
||||
{
|
||||
if (map != SavedMap)
|
||||
{
|
||||
if (SavedMap is null && Find.Maps is { Count: > 0 } maps && maps[0] == map)
|
||||
SavedMap = map;
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
if (SavedHourIntegerTick != TickHelper.TicksGame)
|
||||
return __state = true;
|
||||
|
||||
__result = HourInteger;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Map map, int __result, bool __state)
|
||||
{
|
||||
if (!__state || SavedMap != map)
|
||||
return;
|
||||
|
||||
SavedHourIntegerTick = TickHelper.TicksGame;
|
||||
HourInteger = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SeasonByMap_Patch : FishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Caches results of GenLocalDate.Season for the first map.";
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = (Func<Map, Season>)GenLocalDate.Season;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Map map, ref Season __result, out bool __state)
|
||||
{
|
||||
if (map != SavedMap)
|
||||
{
|
||||
if (SavedMap is null && Find.Maps is { Count: > 0 } maps && maps[0] == map)
|
||||
SavedMap = map;
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
if (SavedSeasonTick != TickHelper.TicksGame)
|
||||
return __state = true;
|
||||
|
||||
__result = Season;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Map map, Season __result, bool __state)
|
||||
{
|
||||
if (!__state || SavedMap != map)
|
||||
return;
|
||||
|
||||
SavedSeasonTick = TickHelper.TicksGame;
|
||||
Season = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map? SavedMap;
|
||||
public static int SavedTick = -2;
|
||||
public static int SavedDayTickTick = -2;
|
||||
public static int SavedHourIntegerTick = -2;
|
||||
public static int SavedSeasonTick = -2;
|
||||
public static int DayTick = -2;
|
||||
public static int HourInteger = -2;
|
||||
public static Season Season = Season.Undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// 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.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
// TODO: benchmark
|
||||
|
||||
public sealed class NeedsTrackerCaching : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class TryGetNeedPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Caches generic need lookups behind the needs list version.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = GetTargetMethod(outParameter: false);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(TryGetNeed<Need>);
|
||||
|
||||
public static T? TryGetNeed<T>(Pawn_NeedsTracker instance) where T : Need
|
||||
=> TryGetNeedInternal(instance, out T? need) ? need : null;
|
||||
}
|
||||
|
||||
public sealed class TryGetNeedOutPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Caches generic need lookups behind the needs list version.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = GetTargetMethod(outParameter: true);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(TryGetNeed<Need>);
|
||||
|
||||
public static bool TryGetNeed<T>(Pawn_NeedsTracker instance, out T? need) where T : Need
|
||||
=> TryGetNeedInternal(instance, out need);
|
||||
}
|
||||
|
||||
private static bool TryGetNeedInternal<T>(Pawn_NeedsTracker instance, out T? need) where T : Need
|
||||
{
|
||||
ref var cache
|
||||
= ref Cache.ByReference<Pawn_NeedsTracker, RuntimeTypeHandle, CacheValue>.GetOrAddReference(instance,
|
||||
typeof(T).TypeHandle);
|
||||
|
||||
var needs = instance.needs;
|
||||
if (!cache.IsDirty(needs))
|
||||
{
|
||||
need = (T?)cache.Need;
|
||||
return cache.Found;
|
||||
}
|
||||
|
||||
return UpdateCache(instance, typeof(T), out need, ref cache);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool UpdateCache<T>(Pawn_NeedsTracker instance, Type needType, out T? need, ref CacheValue cache)
|
||||
where T : Need
|
||||
{
|
||||
var needs = instance.needs;
|
||||
for (var i = 0; i < needs.Count; i++)
|
||||
{
|
||||
var candidate = needs[i];
|
||||
if (candidate.GetType() != needType)
|
||||
continue;
|
||||
|
||||
need = (T)candidate;
|
||||
cache.Update(needs, need, found: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
need = null;
|
||||
cache.Update(needs, null, found: false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static MethodInfo GetTargetMethod(bool outParameter)
|
||||
{
|
||||
foreach (var method in typeof(Pawn_NeedsTracker).GetMethods(AccessTools.all))
|
||||
{
|
||||
if (method.Name != nameof(Pawn_NeedsTracker.TryGetNeed)
|
||||
|| !method.IsGenericMethodDefinition
|
||||
|| method.GetParameters().Length != (outParameter ? 1 : 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
return ThrowMissingMethodException(outParameter);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static MethodInfo ThrowMissingMethodException(bool outParameter)
|
||||
=> throw new MissingMethodException(
|
||||
$"Could not find Pawn_NeedsTracker.TryGetNeed generic overload with {(outParameter ? "one" : "zero")} parameter(s).");
|
||||
|
||||
public record struct CacheValue()
|
||||
{
|
||||
private List<Need>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public Need? Need;
|
||||
public bool Found;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<Need> needs) => !ReferenceEquals(needs, _listReference) || needs._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(List<Need> needs, Need? need, bool found)
|
||||
{
|
||||
_listReference = needs;
|
||||
_listVersion = needs._version;
|
||||
Need = need;
|
||||
Found = found;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,9 @@ public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
if (typeof(FishPatch).IsAssignableFrom(type)
|
||||
&& !type.IsAbstract
|
||||
&& !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
@@ -37,7 +37,9 @@ public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPrepatchBase).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
if (typeof(FishPrepatchBase).IsAssignableFrom(type)
|
||||
&& !type.IsAbstract
|
||||
&& !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPrepatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2026 Bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// 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 Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Rendering;
|
||||
|
||||
public sealed class PawnRenderingOptimizations : ClassWithFishPrepatches
|
||||
{
|
||||
private const int DAMAGE_FLASH_TICKS_TOTAL = 16;
|
||||
|
||||
public sealed class IsHiddenFromPlayerPatch : FishPrepatch
|
||||
{
|
||||
// not 100% on this even benefitting, bionics, scars, etc likely worsen performance here because of the extra check
|
||||
// probably better to just track if a hediff adds invisibility
|
||||
public override string? Description { get; }
|
||||
= "Fast-paths InvisibilityUtility.IsHiddenFromPlayer for the case where a pawn has no hediffs.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(InvisibilityUtility),
|
||||
nameof(InvisibilityUtility.IsHiddenFromPlayer), [typeof(Pawn)]);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ReplacementBody(Pawn pawn)
|
||||
{
|
||||
if (DebugSettings.showHiddenPawns)
|
||||
return false;
|
||||
|
||||
if (pawn.Faction == Faction.OfPlayer)
|
||||
return false;
|
||||
|
||||
var hediffs = pawn.health.hediffSet.hediffs;
|
||||
if (hediffs.Count == 0)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < hediffs.Count; i++)
|
||||
{
|
||||
var comp = hediffs[i].TryGetComp<HediffComp_Invisibility>();
|
||||
if (comp != null && !comp.Props.visibleToPlayer && !comp.PsychologicallyVisible)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PawnRenderTreeComputeMatrixPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Avoids Matrix4x4 inverse work when undoing a pivot translation in PawnRenderTree.ComputeMatrix.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(PawnRenderTree), "ComputeMatrix");
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReplacementBody(PawnRenderTree instance, ref Matrix4x4 matrix, in Vector3 offset,
|
||||
in Vector3 pivot, in Quaternion rotation, in Vector3 scale, bool canRotate)
|
||||
{
|
||||
if (offset != Vector3.zero)
|
||||
matrix *= Matrix4x4.Translate(offset);
|
||||
|
||||
if (pivot != Vector3.zero)
|
||||
matrix *= Matrix4x4.Translate(pivot);
|
||||
|
||||
if (canRotate && rotation != Quaternion.identity)
|
||||
matrix *= Matrix4x4.Rotate(rotation);
|
||||
|
||||
if (scale != Vector3.one)
|
||||
matrix *= Matrix4x4.Scale(scale);
|
||||
|
||||
if (pivot != Vector3.zero)
|
||||
matrix *= Matrix4x4.Translate(-pivot);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DamageFlasherCurColorPatch : FishPrepatch
|
||||
{
|
||||
private static readonly AccessTools.FieldRef<DamageFlasher, int> _lastDamageTick
|
||||
= AccessTools.FieldRefAccess<DamageFlasher, int>("lastDamageTick");
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Fast-paths DamageFlasher.CurColor when no damage flash is active.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredPropertyGetter(typeof(DamageFlasher), nameof(DamageFlasher.CurColor));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Color ReplacementBody(DamageFlasher instance)
|
||||
{
|
||||
var ticksLeft = _lastDamageTick(instance) + DAMAGE_FLASH_TICKS_TOTAL - Find.TickManager.TicksGame;
|
||||
return ticksLeft <= 0
|
||||
? Color.white
|
||||
: Color.Lerp(Color.white, DamagedMatPool.DamagedMatStartingColor,
|
||||
(float)ticksLeft / DAMAGE_FLASH_TICKS_TOTAL);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DamageFlasherGetDamagedMatPatch : FishPrepatch
|
||||
{
|
||||
private static readonly AccessTools.FieldRef<DamageFlasher, int> _lastDamageTick
|
||||
= AccessTools.FieldRefAccess<DamageFlasher, int>("lastDamageTick");
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Fast-paths DamageFlasher.GetDamagedMat when no damage flash is active.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(DamageFlasher), nameof(DamageFlasher.GetDamagedMat));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Material ReplacementBody(DamageFlasher instance, Material baseMat)
|
||||
{
|
||||
var ticksLeft = _lastDamageTick(instance) + DAMAGE_FLASH_TICKS_TOTAL - Find.TickManager.TicksGame;
|
||||
return ticksLeft <= 0
|
||||
? baseMat
|
||||
: DamagedMatPool.GetDamageFlashMat(baseMat, (float)ticksLeft / DAMAGE_FLASH_TICKS_TOTAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ public sealed class TextureAtlasPatches : ClassWithFishPatches
|
||||
{
|
||||
public sealed class StaticTextureAtlas_ApplyTextureCompression : FishPatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Routes texture atlas compression through the GPU path when compute shaders are available, "
|
||||
+ "regardless of the VRAM threshold check in UnityData.ComputeShadersSupported. Prevents native "
|
||||
|
||||
@@ -10,7 +10,6 @@ using System.Security;
|
||||
using System.Threading.Tasks;
|
||||
using PerformanceFish.Cache;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
#if false
|
||||
using ConstructorInfoCache
|
||||
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags,
|
||||
PerformanceFish.System.ReflectionCaching.RecordArray<System.Type>,
|
||||
@@ -57,13 +56,11 @@ using ActivatorCache
|
||||
using CustomAttributeCache
|
||||
= PerformanceFish.Cache.ByReferenceClassic<System.Reflection.ICustomAttributeProvider, System.RuntimeTypeHandle, bool,
|
||||
PerformanceFish.System.ReflectionCaching.MonoCustomAttrs.CustomAttributeCacheValue>;
|
||||
#endif
|
||||
|
||||
namespace PerformanceFish.System;
|
||||
|
||||
public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
#if false
|
||||
static ReflectionCaching() // necessary to prevent recursion between patches and function pointer creation
|
||||
{
|
||||
try
|
||||
@@ -89,7 +86,6 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
ex}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public record struct StateAndFlags
|
||||
{
|
||||
@@ -97,7 +93,6 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
public BindingFlags Flags;
|
||||
}
|
||||
|
||||
#if false
|
||||
public sealed class TypePatches
|
||||
{
|
||||
public sealed class GetField_Patch : FishPatch
|
||||
@@ -387,6 +382,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
|
||||
public sealed class GetFullName : FishPatch
|
||||
{
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Normally averages at over 1000ns per call. Caching makes this about 30 times faster, including "
|
||||
+ "Harmony overhead. Type.Name is similarly slow too, but unfortunately can't be patched as it's an "
|
||||
@@ -397,54 +394,83 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
= AccessTools.DeclaredPropertyGetter(typeof(RuntimeType), nameof(RuntimeType.FullName));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(RuntimeType __instance, ref string? __result, out bool __state)
|
||||
public static bool Prefix(RuntimeType __instance, ref string? __result, out FullNameState __state)
|
||||
{
|
||||
ref var cache = ref TypeFullNameCache.GetOrAddReference(__instance.TypeHandle);
|
||||
__state = default;
|
||||
if (_fullNameRecursionDepth != 0)
|
||||
return true;
|
||||
|
||||
if (!cache.Cached && !TryGetFromCentralCache(__instance, ref cache))
|
||||
return __state = true;
|
||||
__state.ReleaseGuard = true;
|
||||
_fullNameRecursionDepth++;
|
||||
|
||||
var cache = TypeFullNameCache.GetOrAddReference(__instance.TypeHandle);
|
||||
var fetchedFromCentralCache = false;
|
||||
if (!cache.Cached)
|
||||
{
|
||||
fetchedFromCentralCache = TryGetFromCentralCache(__instance, out cache);
|
||||
if (!fetchedFromCentralCache)
|
||||
{
|
||||
__state.ShouldStore = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchedFromCentralCache)
|
||||
TypeFullNameCache.GetExistingReference(__instance.TypeHandle) = cache;
|
||||
|
||||
__result = cache.Name;
|
||||
return __state = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool TryGetFromCentralCache(RuntimeType instance,
|
||||
ref TypeFullNameCacheValue cache)
|
||||
out TypeFullNameCacheValue cache)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
ref var centralCache
|
||||
= ref TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
|
||||
cache.Name = centralCache.Name;
|
||||
cache = TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
|
||||
if (!cache.Cached)
|
||||
return false;
|
||||
}
|
||||
|
||||
return cache.Cached;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(RuntimeType __instance, string? __result, bool __state)
|
||||
public static void Postfix(RuntimeType __instance, string? __result, FullNameState __state)
|
||||
{
|
||||
if (!__state)
|
||||
if (!__state.ShouldStore)
|
||||
return;
|
||||
|
||||
UpdateCache(__instance, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Exception? Finalizer(Exception? __exception, ref FullNameState __state)
|
||||
{
|
||||
if (__state.ReleaseGuard)
|
||||
_fullNameRecursionDepth--;
|
||||
|
||||
return __exception;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(RuntimeType __instance, string? __result)
|
||||
{
|
||||
ref var cache = ref TypeFullNameCache.GetExistingReference(__instance.TypeHandle);
|
||||
cache.Name = __result;
|
||||
var cache = new TypeFullNameCacheValue
|
||||
{
|
||||
Cached = true,
|
||||
Name = __result
|
||||
};
|
||||
TypeFullNameCache.GetExistingReference(__instance.TypeHandle) = cache;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
ref var centralCache
|
||||
= ref TypeFullNameCache.GetDirectly.GetReference(__instance.TypeHandle);
|
||||
centralCache.Name = __result;
|
||||
}
|
||||
TypeFullNameCache.GetDirectly.GetOrAddReference(__instance.TypeHandle) = cache;
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static int _fullNameRecursionDepth;
|
||||
|
||||
private static object _lock = new();
|
||||
}
|
||||
|
||||
@@ -479,24 +505,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
public PropertyInfo? Info;
|
||||
}
|
||||
|
||||
public record struct TypeFullNameCacheValue()
|
||||
public record struct FullNameState
|
||||
{
|
||||
private object? _value = typeof(void);
|
||||
|
||||
public string? Name
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Unsafe.As<string?>(_value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => _value = value;
|
||||
public bool ReleaseGuard;
|
||||
public bool ShouldStore;
|
||||
}
|
||||
|
||||
public bool Cached
|
||||
public record struct TypeFullNameCacheValue
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _value is not MemberInfo;
|
||||
}
|
||||
public bool Cached;
|
||||
public string? Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,6 +522,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public sealed class GetCustomAttributes : FishPatch
|
||||
{
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Caches attributes for reflection lookups. Decent load time improvement.";
|
||||
|
||||
@@ -538,17 +558,23 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static CustomAttributeCacheLookup TryGetCachedAttributes(ref CustomAttributeCache key,
|
||||
out object[]? result)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var localLookup = TryUseCache(CustomAttributeCache.Get, ref key, out result);
|
||||
if (localLookup != CustomAttributeCacheLookup.Miss)
|
||||
if (localLookup == CustomAttributeCacheLookup.Hit)
|
||||
{
|
||||
result = CopyCachedAttributes(result!);
|
||||
return CustomAttributeCacheLookup.Hit;
|
||||
}
|
||||
|
||||
if (localLookup == CustomAttributeCacheLookup.InProgress)
|
||||
return localLookup;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var centralLookup = TryUseCache(CustomAttributeCache.GetDirectly, ref key, out var centralResult);
|
||||
if (centralLookup == CustomAttributeCacheLookup.Hit)
|
||||
{
|
||||
result = centralResult;
|
||||
result = CopyCachedAttributes(centralResult!);
|
||||
CustomAttributeCache.Get[key] = new()
|
||||
{
|
||||
Attributes = centralResult,
|
||||
@@ -567,19 +593,19 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(ICustomAttributeProvider obj, object[]? __result,
|
||||
in CustomAttributeState __state)
|
||||
ref CustomAttributeState __state)
|
||||
{
|
||||
if (!__state.ShouldStore || __result is null)
|
||||
return;
|
||||
|
||||
StoreCompleted(ref Unsafe.AsRef(in __state.Key), __result);
|
||||
StoreCompleted(ref __state.Key, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static Exception? Finalizer(Exception? __exception, in CustomAttributeState __state)
|
||||
public static Exception? Finalizer(Exception? __exception, ref CustomAttributeState __state)
|
||||
{
|
||||
if (__state.ShouldStore && __exception != null)
|
||||
ClearInProgress(ref Unsafe.AsRef(in __state.Key));
|
||||
ClearInProgress(ref __state.Key);
|
||||
|
||||
return __exception;
|
||||
}
|
||||
@@ -630,7 +656,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
var value = new CustomAttributeCacheValue
|
||||
{
|
||||
Attributes = result,
|
||||
Attributes = CopyCachedAttributes(result),
|
||||
Status = CustomAttributeCacheStatus.Completed
|
||||
};
|
||||
|
||||
@@ -665,6 +691,10 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static object[] CopyCachedAttributes(object[] attributes)
|
||||
=> attributes.Length != 0 ? (object[])attributes.Clone() : Array.Empty<object>();
|
||||
|
||||
private static object _lock = new();
|
||||
}
|
||||
|
||||
@@ -707,8 +737,12 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
Guard.IsNotNull(array);
|
||||
Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue);
|
||||
Array = array;
|
||||
var length = array.Length;
|
||||
_hashCode = HashCode.Combine(length, length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
|
||||
var hashCode = array.Length;
|
||||
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
hashCode = global::System.HashCode.Combine(hashCode, array[i]);
|
||||
|
||||
_hashCode = hashCode;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -717,13 +751,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
if (Array is null)
|
||||
return other.Array is null;
|
||||
|
||||
if (_hashCode != other._hashCode)
|
||||
return false;
|
||||
|
||||
if (other.Array is null || Array.Length != other.Array.Length)
|
||||
return false;
|
||||
|
||||
var length = Array.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (!Array[i].Equals(other.Array![i]))
|
||||
if (!EqualityComparer<T>.Default.Equals(Array[i], other.Array[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -738,6 +775,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public sealed class CreateInstance_Type : FishPatch
|
||||
{
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
|
||||
|
||||
public override string? Description { get; }
|
||||
@@ -749,21 +788,94 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type? type, ref object __result)
|
||||
{
|
||||
if (type is null)
|
||||
if (type is not RuntimeType runtimeType)
|
||||
return true;
|
||||
|
||||
ref var cache = ref ActivatorCache.GetOrAddReference(type.TypeHandle);
|
||||
ref var cache = ref ActivatorCache.GetOrAddReference(runtimeType.TypeHandle);
|
||||
|
||||
if (ReferenceEquals(cache.Result, _unsupportedActivatorSentinel))
|
||||
return true;
|
||||
|
||||
if (cache.Result is not null)
|
||||
return TryInvoke(ref __result, cache.Result);
|
||||
|
||||
if (!CanUseFastPath(runtimeType))
|
||||
return MarkUnsupported(runtimeType, ref cache);
|
||||
|
||||
if (cache.Dirty
|
||||
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, Type, Func<object>>(ref cache,
|
||||
type.TypeHandle, type))
|
||||
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, RuntimeType, Func<object>>(ref cache,
|
||||
runtimeType.TypeHandle, runtimeType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
__result = cache.Result!();
|
||||
return false;
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new TargetInvocationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool MarkUnsupported(RuntimeType type, ref ActivatorCacheValue cache)
|
||||
{
|
||||
cache.Result = _unsupportedActivatorSentinel;
|
||||
|
||||
lock (ActivatorCache.SyncLock)
|
||||
ActivatorCache.GetDirectly.GetOrAddReference(type.TypeHandle).Result = _unsupportedActivatorSentinel;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool TryInvoke(ref object __result, Func<object> activator)
|
||||
{
|
||||
try
|
||||
{
|
||||
__result = activator();
|
||||
return false;
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new TargetInvocationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanUseFastPath(RuntimeType type)
|
||||
{
|
||||
if (type.HasElementType
|
||||
|| type.ContainsGenericParameters
|
||||
|| type.IsInterface
|
||||
|| type.IsAbstract
|
||||
|| type.IsCOMObject
|
||||
|| type == typeof(void)
|
||||
|| type == typeof(TypedReference)
|
||||
|| type == typeof(ArgIterator)
|
||||
|| type == typeof(RuntimeArgumentHandle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return type.IsValueType
|
||||
|| type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)
|
||||
is not null;
|
||||
}
|
||||
|
||||
private static readonly Func<object> _unsupportedActivatorSentinel
|
||||
= static () => throw new InvalidOperationException("Unsupported activator sentinel");
|
||||
}
|
||||
|
||||
// public sealed class CreateInstance_Type_Args : FishPatch
|
||||
@@ -772,14 +884,34 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
// = (Func<Type, object[], object>)Activator.CreateInstance;
|
||||
// }
|
||||
|
||||
public record struct ActivatorCacheValue : IAsyncCacheable<RuntimeTypeHandle, Type, Func<object>>
|
||||
public record struct ActivatorCacheValue : IAsyncCacheable<RuntimeTypeHandle, RuntimeType, Func<object>>
|
||||
{
|
||||
public Func<object>? Result { get; set; }
|
||||
public Task<Func<object>>? Task { get; set; }
|
||||
public bool Dirty => Result == null;
|
||||
|
||||
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, Type type)
|
||||
=> new(Reflection.CreateConstructorDelegate<Func<object>>(type));
|
||||
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, RuntimeType type)
|
||||
=> new(MakeActivatorDelegate(type));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static Func<object>? MakeActivatorDelegate(RuntimeType type)
|
||||
{
|
||||
if (type.IsValueType)
|
||||
return Reflection.CreateConstructorDelegate<Func<object>>(type);
|
||||
|
||||
var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null,
|
||||
Type.EmptyTypes, null);
|
||||
|
||||
if (constructor is null)
|
||||
return null;
|
||||
|
||||
var dynamicMethod = new DynamicMethod($"Activator_{type.Name}", typeof(object), Type.EmptyTypes,
|
||||
type, true);
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
il.Emit(FishTranspiler.New(constructor));
|
||||
il.Emit(FishTranspiler.Return);
|
||||
return (Func<object>)dynamicMethod.CreateDelegate(typeof(Func<object>));
|
||||
}
|
||||
}
|
||||
|
||||
// public record struct ActivatorWithArgumentsCacheValue : IAsyncCacheable<ActivatorWithArgumentsCache, Type,
|
||||
@@ -869,6 +1001,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public sealed class GetValue_Patch : FishPatch
|
||||
{
|
||||
public override bool DefaultState => true;
|
||||
|
||||
public override string Description { get; }
|
||||
= "Optimizes the GetValue method by invoking it through specialized cached delegates";
|
||||
|
||||
@@ -890,6 +1024,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
|
||||
{
|
||||
ValidateGetValueCall(__instance, obj);
|
||||
|
||||
ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
|
||||
|
||||
if (cache.Dirty
|
||||
@@ -902,10 +1038,36 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
__result = cache.Result!(obj);
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ValidateGetValueCall(FieldInfo info, object? obj)
|
||||
{
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
if (obj is null)
|
||||
throw new TargetException("Non-static field requires a target");
|
||||
|
||||
var declaringType = info.DeclaringType!;
|
||||
if (!declaringType.IsAssignableFrom(obj.GetType()))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Field {info.Name} defined on type {declaringType} is not a field on the target object which is of type {obj.GetType()}.",
|
||||
nameof(obj));
|
||||
}
|
||||
}
|
||||
|
||||
if (!info.IsLiteral && info.DeclaringType!.ContainsGenericParameters)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SetValue_Patch : FishPatch
|
||||
{
|
||||
public override bool DefaultState => false;
|
||||
|
||||
public override string Description { get; }
|
||||
= "Optimizes the SetValue method by invoking it through specialized cached delegates";
|
||||
|
||||
@@ -927,14 +1089,20 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
|
||||
[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,
|
||||
{
|
||||
if (__instance.IsConst())
|
||||
return ErrorForInvalidCallAttempt(__instance, ref cache);
|
||||
|
||||
return FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
|
||||
__instance.FieldHandle, __instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool ErrorForInvalidCallAttempt(FieldInfo info, ref FieldInfoSetterCache cache)
|
||||
{
|
||||
if (cache.Task == _fieldSetterFailureTask)
|
||||
return false;
|
||||
|
||||
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 "
|
||||
@@ -944,7 +1112,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!);
|
||||
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!); // sentinel only
|
||||
|
||||
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
|
||||
{
|
||||
@@ -1117,13 +1285,15 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
if (methodInfo is null && constructorInfo is null)
|
||||
return null;
|
||||
|
||||
var parameters = methodBase.GetParameters();
|
||||
if (!CanCacheInvoke(methodBase, methodInfo, constructorInfo, parameters))
|
||||
return null;
|
||||
|
||||
var dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
|
||||
[typeof(object), typeof(object[])],
|
||||
GetOwnerType(methodBase), true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
var parameters = methodBase.GetParameters();
|
||||
|
||||
if (methodInfo != null && !methodBase.IsStatic)
|
||||
EmitInstanceArgument(il, methodBase);
|
||||
|
||||
@@ -1158,6 +1328,49 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanCacheInvoke(MethodBase methodBase, MethodInfo? methodInfo,
|
||||
ConstructorInfo? constructorInfo, ParameterInfo[] parameters)
|
||||
{
|
||||
if (methodBase.ContainsGenericParameters
|
||||
|| (methodBase.CallingConvention & CallingConventions.VarArgs) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (methodInfo != null && !IsSupportedInvokeType(methodInfo.ReturnType))
|
||||
return false;
|
||||
|
||||
if (constructorInfo?.DeclaringType is { } declaringType
|
||||
&& !IsSupportedInvokeType(declaringType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (!IsSupportedInvokeParameter(parameters[i].ParameterType))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSupportedInvokeParameter(Type parameterType)
|
||||
{
|
||||
// object[] stores nullable value arguments as boxed T or null, not boxed Nullable<T>, so the
|
||||
// current byref fast path cannot safely hand out a Nullable<T>&.
|
||||
if (parameterType.IsByRef && parameterType.GetElementType()!.IsNullable())
|
||||
return false;
|
||||
|
||||
if (parameterType.IsByRef)
|
||||
parameterType = parameterType.GetElementType()!;
|
||||
|
||||
return IsSupportedInvokeType(parameterType);
|
||||
}
|
||||
|
||||
private static bool IsSupportedInvokeType(Type type)
|
||||
=> !type.IsPointer && !type.ContainsGenericParameters;
|
||||
|
||||
private static void EmitInvokeMethodCall(MethodBase methodBase, ILGenerator il, MethodInfo methodInfo)
|
||||
{
|
||||
il.Emit(FishTranspiler.Call(methodBase));
|
||||
@@ -1252,7 +1465,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[SecuritySafeCritical]
|
||||
public static ref TTo CastRefSafely<TTo>(ref object? from)
|
||||
public static ref TTo CastRefSafely<TTo>(ref object? from) where TTo : class?
|
||||
=> ref from is TTo or null
|
||||
? ref Unsafe.As<object?, TTo>(ref from)
|
||||
: ref ThrowInvalidCastException<TTo>();
|
||||
@@ -1264,7 +1477,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
? ref Unsafe.Unbox<TTo>(from)
|
||||
: ref Unsafe.Unbox<TTo>(from
|
||||
= from is null
|
||||
? default
|
||||
? default(TTo)
|
||||
: FisheryLib.Convert.Type<object, TTo>(from));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1314,5 +1527,4 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
||||
// populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and
|
||||
// MakeSetterDelegate
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user