// 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); public static T? TryGetNeed(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); public static bool TryGetNeed(Pawn_NeedsTracker instance, out T? need) where T : Need => TryGetNeedInternal(instance, out need); } private static bool TryGetNeedInternal(Pawn_NeedsTracker instance, out T? need) where T : Need { ref var cache = ref Cache.ByReference.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(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? _listReference; private int _listVersion = -2; public Need? Need; public bool Found; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsDirty(List needs) => !ReferenceEquals(needs, _listReference) || needs._version != _listVersion; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(List needs, Need? need, bool found) { _listReference = needs; _listVersion = needs._version; Need = need; Found = found; } } }