// 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; public sealed class GeneTrackerOptimization : ClassWithFishPrepatches { public sealed class GeneTrackerTickPatch : FishPrepatch { private static readonly FishTable _skippableTypes; static GeneTrackerTickPatch() { _skippableTypes = new() { ValueInitializer = static type => ComputeIsSkippableType(type) }; } public override string? Description { get; } = "Every pawn has a gene tracker, which is responsible for ticking each of their genes. Normally it ticks " + "all of them equally, including those don't change or affect anything through ticking, like skin colors " + "or basic stat modifiers. This patch improves the gene tracker to determine genes that need ticking in " + "advance, cache the list of them, and only tick those, skipping all the others."; public override MethodBase TargetMethodBase { get; } = AccessTools.Method(typeof(Pawn_GeneTracker), nameof(Pawn_GeneTracker.GeneTrackerTick)); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(GeneTrackerTick); public static void GeneTrackerTick(Pawn_GeneTracker instance) { if (!ModLister.BiotechInstalled) return; var genesToTick = instance.GenesToTick(); if (Dirty(instance, genesToTick)) Update(instance, genesToTick); for (var i = genesToTick.Count - 1; i >= 0; i--) { if (genesToTick[i].Active) genesToTick[i].Tick(); } if (instance.pawn.IsSpawned() && instance.Xenotype != XenotypeDefOf.Baseliner && instance.pawn.IsHashIntervalTick(300)) { LessonAutoActivator.TeachOpportunity(ConceptDefOf.GenesAndXenotypes, OpportunityType.Important); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Dirty(Pawn_GeneTracker instance, List genesToTick) #if V1_6 { ref var versions = ref instance.GeneListVersions(); return !versions.Initialized || versions.Xenogenes != instance.xenogenes._version || versions.Endogenes != instance.endogenes._version; } #else => GetListVersion(instance) != genesToTick._version; #endif [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(Pawn_GeneTracker instance, List genesToTick) { genesToTick.Clear(); AddTickableGenes(genesToTick, instance.xenogenes); AddTickableGenes(genesToTick, instance.endogenes); #if V1_6 ref var versions = ref instance.GeneListVersions(); versions.Xenogenes = instance.xenogenes._version; versions.Endogenes = instance.endogenes._version; versions.Initialized = true; #else genesToTick._version = GetListVersion(instance); #endif } #if !V1_6 [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetListVersion(Pawn_GeneTracker instance) => (instance.xenogenes._version << 16) | (instance.endogenes._version & 0b1111_1111_1111_1111); #endif private static void AddTickableGenes(List genesToTick, List genes) { var count = genes.Count; for (var i = 0; i < count; i++) { var gene = genes[i]; if (IsSkippableType(gene.GetType()) && (gene.def.mentalBreakMtbDays <= 0f || gene.def.mentalBreakDef == null)) { continue; } genesToTick.Add(gene); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsSkippableType(Type type) => _skippableTypes.GetOrAdd(type); private static bool ComputeIsSkippableType(Type type) { if (type == typeof(Gene)) return true; var tickMethod = type.GetMethod(nameof(Gene.Tick), global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null); var tickIntervalMethod = type.GetMethod(nameof(Gene.TickInterval), global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic, null, [typeof(int)], null); return tickMethod?.DeclaringType == typeof(Gene) && tickIntervalMethod?.DeclaringType == typeof(Gene); } } #if V1_6 public struct GeneListVersionState { public int Xenogenes; public int Endogenes; public bool Initialized; } #endif public sealed class GeneTrackerTickIntervalPatch : FishPrepatch { // i am iffy about this public override string? Description { get; } = "Every pawn has a gene tracker, which is responsible for ticking each of their genes. Normally it ticks " + "all of them equally, including those don't change or affect anything through ticking, like skin colors " + "or basic stat modifiers. This patch improves the gene tracker to determine genes that need ticking in " + "advance, cache the list of them, and only tick those, skipping all the others."; public override MethodBase TargetMethodBase { get; } = AccessTools.Method(typeof(Pawn_GeneTracker), nameof(Pawn_GeneTracker.GeneTrackerTickInterval)); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(GeneTrackerTickInterval); public static void GeneTrackerTickInterval(Pawn_GeneTracker instance, int delta) { if (!ModLister.BiotechInstalled) return; var genesToTick = instance.GenesToTick(); if (GeneTrackerTickPatch.Dirty(instance, genesToTick)) GeneTrackerTickPatch.Update(instance, genesToTick); for (var i = genesToTick.Count - 1; i >= 0; i--) { if (genesToTick[i].Active) genesToTick[i].TickInterval(delta); } if (instance.pawn.IsSpawned() && instance.Xenotype != XenotypeDefOf.Baseliner && instance.pawn.IsHashIntervalTick(300, delta)) { LessonAutoActivator.TeachOpportunity(ConceptDefOf.GenesAndXenotypes, OpportunityType.Important); } } } }