diff --git a/1.5/Assemblies/PerformanceFish.dll b/1.5/Assemblies/PerformanceFish.dll index 24304e8..eacfdc0 100644 Binary files a/1.5/Assemblies/PerformanceFish.dll and b/1.5/Assemblies/PerformanceFish.dll differ diff --git a/1.5/Assemblies/PerformanceFish.pdb b/1.5/Assemblies/PerformanceFish.pdb index 312e985..4771ea9 100644 Binary files a/1.5/Assemblies/PerformanceFish.pdb and b/1.5/Assemblies/PerformanceFish.pdb differ diff --git a/1.6/Assemblies/PerformanceFish.dll b/1.6/Assemblies/PerformanceFish.dll new file mode 100644 index 0000000..6c995e0 Binary files /dev/null and b/1.6/Assemblies/PerformanceFish.dll differ diff --git a/1.6/Assemblies/PerformanceFish.pdb b/1.6/Assemblies/PerformanceFish.pdb new file mode 100644 index 0000000..b83ab76 Binary files /dev/null and b/1.6/Assemblies/PerformanceFish.pdb differ diff --git a/About/About.xml b/About/About.xml index 4817d54..78aa9e3 100644 --- a/About/About.xml +++ b/About/About.xml @@ -5,6 +5,7 @@
  • 1.4
  • 1.5
  • +
  • 1.6
  • bradson PerformanceFish/ModIcon @@ -56,4 +57,4 @@
  • vanillaexpanded.storytellers.rick
  • download more tps - \ No newline at end of file + diff --git a/Source/PerformanceFish/1.5.csproj b/Source/PerformanceFish/1.5.csproj index baf5a13..7379b8e 100644 --- a/Source/PerformanceFish/1.5.csproj +++ b/Source/PerformanceFish/1.5.csproj @@ -3,6 +3,8 @@ ..\..\1.5\Assemblies\ Copyright (c) 2024 Bradson + $(MSBuildThisFileDirectory)..\..\..\Fishery + $(MSBuildThisFileDirectory)..\..\..\Fishery-clean DEBUG;V1_5 @@ -14,7 +16,7 @@ - ..\..\..\Fishery\1.5\Assemblies\1Fishery.dll + $(FisheryRepoRoot)\1.5\Assemblies\1Fishery.dll False diff --git a/Source/PerformanceFish/1.6.csproj b/Source/PerformanceFish/1.6.csproj new file mode 100644 index 0000000..1478547 --- /dev/null +++ b/Source/PerformanceFish/1.6.csproj @@ -0,0 +1,23 @@ + + + + ..\..\1.6\Assemblies\ + Copyright (c) 2026 Bradson + $(MSBuildThisFileDirectory)..\..\..\Fishery + $(MSBuildThisFileDirectory)..\..\..\Fishery-clean + + + DEBUG;V1_5;V1_6 + + + V1_5;V1_6 + + + + + + $(FisheryRepoRoot)\1.6\Assemblies\1Fishery.dll + False + + + diff --git a/Source/PerformanceFish/Cache/BitCellGrid.cs b/Source/PerformanceFish/Cache/BitCellGrid.cs index 9e99ea6..1027cc4 100644 --- a/Source/PerformanceFish/Cache/BitCellGrid.cs +++ b/Source/PerformanceFish/Cache/BitCellGrid.cs @@ -7,13 +7,15 @@ namespace PerformanceFish.Cache; public sealed class BitCellGrid { + private readonly Map _map; private long[] _innerArray; private CellIndices _cellIndices; public BitCellGrid(Map map) { - _innerArray = null!; - _cellIndices = null!; + _map = map; + _innerArray = Array.Empty(); + _cellIndices = default; map.InvokeWhenCellIndicesReady(Initialize); } @@ -23,12 +25,26 @@ public sealed class BitCellGrid _innerArray = new long[(Math.Max(0, _cellIndices.NumGridCells - 1) >> 6) + 1]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (_cellIndices.NumGridCells != _map.cellIndices.NumGridCells) + Initialize(_map); + } + public bool this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_innerArray[index >> 6] & (1L << (index & 63))) != 0L; + get + { + EnsureInitialized(); + ValidateIndex(index); + return (_innerArray[index >> 6] & (1L << (index & 63))) != 0L; + } set { + EnsureInitialized(); + ValidateIndex(index); ref var bucket = ref _innerArray[index >> 6]; bucket ^= (-(long)value.AsInt() ^ bucket) & (1L << (index & 63)); } @@ -37,7 +53,24 @@ public sealed class BitCellGrid public bool this[in IntVec3 c] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[c.CellToIndex(_cellIndices)]; - set => this[c.CellToIndex(_cellIndices)] = value; + get + { + EnsureInitialized(); + return this[c.CellToIndex(_cellIndices)]; + } + set + { + EnsureInitialized(); + this[c.CellToIndex(_cellIndices)] = value; + } } -} \ No newline at end of file + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ValidateIndex(int index) + { + if ((uint)index >= (uint)_cellIndices.NumGridCells) + { + throw new IndexOutOfRangeException($"BitCellGrid index {index} out of bounds for {_cellIndices.NumGridCells} cells on map size {_map.Size}."); + } + } +} diff --git a/Source/PerformanceFish/Cache/CellGrid.cs b/Source/PerformanceFish/Cache/CellGrid.cs index d83ad3f..9d964e5 100644 --- a/Source/PerformanceFish/Cache/CellGrid.cs +++ b/Source/PerformanceFish/Cache/CellGrid.cs @@ -9,38 +9,60 @@ namespace PerformanceFish.Cache; public sealed class CellGrid { + private readonly Map _map; private T[] _innerArray; private CellIndices _cellIndices; public CellGrid(Map map) { - _innerArray = null!; - _cellIndices = null!; + _map = map; + _innerArray = Array.Empty(); + _cellIndices = default; map.InvokeWhenCellIndicesReady(Initialize); } - [MemberNotNull(nameof(_innerArray), nameof(_cellIndices))] + [MemberNotNull(nameof(_innerArray))] private void Initialize(Map map) { _cellIndices = map.cellIndices; _innerArray = new T[_cellIndices.NumGridCells]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureInitialized() + { + if (_cellIndices.NumGridCells != _map.cellIndices.NumGridCells) + Initialize(_map); + } + public ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _innerArray[index]; + get + { + EnsureInitialized(); + return ref _innerArray[index]; + } } public ref T this[in IntVec3 c] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _innerArray[c.CellToIndex(_cellIndices)]; + get + { + EnsureInitialized(); + var index = c.CellToIndex(_cellIndices); + return ref _innerArray[index]; + } } public ref T this[CellIndex cellIndex] { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _innerArray[cellIndex.Value]; + get + { + EnsureInitialized(); + return ref _innerArray[cellIndex.Value]; + } } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/CellIndex.cs b/Source/PerformanceFish/CellIndex.cs index 731b6c4..e2f544f 100644 --- a/Source/PerformanceFish/CellIndex.cs +++ b/Source/PerformanceFish/CellIndex.cs @@ -10,20 +10,25 @@ public record struct CellIndex(int Value) public int Value = Value; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CellIndex(IntVec3 cell, Map map) : this(cell, map.cellIndices.mapSizeX) {} + public CellIndex(IntVec3 cell, Map map) : this(cell, map.Size.x) {} [MethodImpl(MethodImplOptions.AggressiveInlining)] public CellIndex(IntVec3 cell, int mapSizeX) : this(cell.CellToIndex(mapSizeX)) {} - public IntVec3 ToCell(Map map) => ToCell(map.cellIndices.mapSizeX); + public IntVec3 ToCell(Map map) => ToCell(map.Size.x); public IntVec3 ToCell(int mapSizeX) => CellIndicesUtility.IndexToCell(Value, mapSizeX); public List GetThingList(Map map) => map.thingGrid.thingGrid[Value]; - public bool IsFogged(Map map) => map.fogGrid.fogGrid[Value]; + public bool IsFogged(Map map) +#if V1_6 + => map.fogGrid.fogGrid.IsSet(Value); +#else + => map.fogGrid.fogGrid[Value]; +#endif public float GetSnowDepth(Map map) => map.snowGrid.depthGrid[Value]; public Building? GetEdifice(Map map) => map.edificeGrid[Value]; -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Defs/ThingDefPatches.cs b/Source/PerformanceFish/Defs/ThingDefPatches.cs index ffc0c67..ac98f4d 100644 --- a/Source/PerformanceFish/Defs/ThingDefPatches.cs +++ b/Source/PerformanceFish/Defs/ThingDefPatches.cs @@ -21,13 +21,25 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches public sealed class GetHashCodePatch : FishClassPrepatch { public override string? Description { get; } +#if V1_6 + = "Disabled on 1.6 because Verse.Def now already caches defNameHash and overrides GetHashCode itself."; +#else = "Def.GetHashCode is overridden to return defName.GetHashCode, which is rather slow as that's a string. " + "This patch caches the hashcode in a field added through prepatcher and simply returns that instead."; +#endif - public override Type Type { get; } = typeof(BuildableDef); + public override Type Type { get; } = +#if V1_6 + typeof(Def); +#else + typeof(BuildableDef); +#endif public override void FreePatch(TypeDefinition typeDefinition) { +#if V1_6 + return; +#else var module = typeDefinition.Module; var fishHashField = new FieldDefinition(FISH_HASH_FIELD_NAME, FieldAttributes.Private, @@ -74,6 +86,7 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches getHashCodeBody.MaxStackSize = 2; getHashCodeBody.Method.ImplAttributes |= MethodImplAttributes.AggressiveInlining; getHashCodeBody.OptimizeMacros(); +#endif } // [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -295,4 +308,4 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches Flammability, MaxHitPoints } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Directory.Build.props b/Source/PerformanceFish/Directory.Build.props index cc90b52..84db8c9 100644 --- a/Source/PerformanceFish/Directory.Build.props +++ b/Source/PerformanceFish/Directory.Build.props @@ -29,12 +29,13 @@ - + + @@ -93,4 +94,4 @@ - \ No newline at end of file + diff --git a/Source/PerformanceFish/GasGridOptimization.cs b/Source/PerformanceFish/GasGridOptimization.cs index 3b5e3bf..a69bd8c 100644 --- a/Source/PerformanceFish/GasGridOptimization.cs +++ b/Source/PerformanceFish/GasGridOptimization.cs @@ -41,7 +41,8 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches = "Required by the gas grid optimization"; public override MethodBase TargetMethodBase { get; } - = AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.SetDirect)); + = AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.SetDirect), + [typeof(int), typeof(byte), typeof(byte), typeof(byte), typeof(byte)])!; public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(ReplacementBody); @@ -415,7 +416,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetMapSizeX(Map map) => map.cellIndices.mapSizeX; + public static int GetMapSizeX(Map map) => map.Size.x; } public sealed class ExposeDataPatch : FishPrepatch @@ -1075,7 +1076,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches [MethodImpl(MethodImplOptions.AggressiveInlining)] private IntVec3 IndexToCell(int index) - => CellIndicesUtility.IndexToCell(index, Map.cellIndices.mapSizeX); + => CellIndicesUtility.IndexToCell(index, Map.Size.x); /// /// For modders. Performance Fish doesn't automatically scribe added custom defs. @@ -1136,4 +1137,4 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches } // ReSharper restore UnusedMember.Local } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/GenTypesPatches.cs b/Source/PerformanceFish/GenTypesPatches.cs index 105cc95..e4f48f2 100644 --- a/Source/PerformanceFish/GenTypesPatches.cs +++ b/Source/PerformanceFish/GenTypesPatches.cs @@ -43,14 +43,15 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches public static List ReplacementBody() where TAttr : Attribute { + var attributeType = typeof(TAttr); lock (Lock) { - var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(typeof(TAttr)); + var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(attributeType); if (typesWithAttribute is null) { - GenTypes.cachedTypesWithAttribute.Add(typeof(TAttr), - typesWithAttribute = GenTypes.AllTypes.AsParallel() + GenTypes.cachedTypesWithAttribute.Add(attributeType, + typesWithAttribute = GenTypes.AllTypes .Where(Predicate()).ToList()); } @@ -82,23 +83,14 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches if (subclasses is null) { - ((PredicateClass)Predicate.Target).BaseType = baseType; GenTypes.cachedSubclasses.Add(baseType, - subclasses = GenTypes.AllTypes.AsParallel() - .Where(Predicate).ToList()); + subclasses = GenTypes.AllTypes + .Where(type => type.IsSubclassOf(baseType)).ToList()); } return subclasses; } } - - public static Func Predicate = new PredicateClass().Invoke; - - public sealed class PredicateClass - { - public Type? BaseType; - public bool Invoke(Type type) => type.IsSubclassOf(BaseType!); - } } public sealed class AllSubclassesNonAbstract : FishPrepatch @@ -123,7 +115,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches if (subclassesNonAbstract is null) { GenTypes.cachedSubclassesNonAbstract.Add(baseType, - subclassesNonAbstract = baseType.AllSubclasses().AsParallel() + subclassesNonAbstract = baseType.AllSubclasses() .Where(Predicate).ToList()); } @@ -133,4 +125,4 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches public static Func Predicate = static subClass => !subClass.IsAbstract; } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/GeneTrackerOptimization.cs b/Source/PerformanceFish/GeneTrackerOptimization.cs index 242358d..87d88d8 100644 --- a/Source/PerformanceFish/GeneTrackerOptimization.cs +++ b/Source/PerformanceFish/GeneTrackerOptimization.cs @@ -14,6 +14,16 @@ 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 " @@ -74,7 +84,7 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches for (var i = 0; i < count; i++) { var gene = genes[i]; - if (SkippableTypes.Contains(gene.GetType()) + if (IsSkippableType(gene.GetType()) && (gene.def.mentalBreakMtbDays <= 0f || gene.def.mentalBreakDef == null)) { continue; @@ -83,8 +93,15 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches genesToTick.Add(gene); } } - - public static HashSet SkippableTypes - = typeof(Gene).SubclassesWithNoMethodOverrideAndSelf(nameof(Gene.Tick)).ToHashSet(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSkippableType(Type type) => _skippableTypes.GetOrAdd(type); + + private static bool ComputeIsSkippableType(Type type) + => type == typeof(Gene) + || type.GetMethod(nameof(Gene.Tick), global::System.Reflection.BindingFlags.Instance + | global::System.Reflection.BindingFlags.Public + | global::System.Reflection.BindingFlags.NonPublic + | global::System.Reflection.BindingFlags.DeclaredOnly, null, Type.EmptyTypes, null) == null; } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/GlobalUsings.cs b/Source/PerformanceFish/GlobalUsings.cs new file mode 100644 index 0000000..ee3aa06 --- /dev/null +++ b/Source/PerformanceFish/GlobalUsings.cs @@ -0,0 +1 @@ +global using HashCode = FisheryLib.HashCode; diff --git a/Source/PerformanceFish/Listers/Buildings.cs b/Source/PerformanceFish/Listers/Buildings.cs index 32cae14..bba1cdd 100644 --- a/Source/PerformanceFish/Listers/Buildings.cs +++ b/Source/PerformanceFish/Listers/Buildings.cs @@ -237,6 +237,11 @@ public sealed class Buildings : ClassWithFishPrepatches if (def.building is { isNaturalRock: true }) return; + UpdateCacheOnAdd(__instance, b, def); + } + + private static void UpdateCacheOnAdd(ListerBuildings __instance, Building b, ThingDef def) + { var cache = __instance.Cache(); if (b.Faction == Faction.OfPlayer) @@ -290,6 +295,11 @@ public sealed class Buildings : ClassWithFishPrepatches if (def.building is { isNaturalRock: true }) return; + UpdateCacheOnRemove(__instance, b, def); + } + + private static void UpdateCacheOnRemove(ListerBuildings __instance, Building b, ThingDef def) + { var cache = __instance.Cache(); if (b.Faction == Faction.OfPlayer) @@ -334,4 +344,4 @@ public sealed class Buildings : ClassWithFishPrepatches => (IList)Activator.CreateInstance(typeof(IndexedFishSet<>).MakeGenericType(type)) }; } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Listers/Haulables.cs b/Source/PerformanceFish/Listers/Haulables.cs index 6fd8c4f..384c511 100644 --- a/Source/PerformanceFish/Listers/Haulables.cs +++ b/Source/PerformanceFish/Listers/Haulables.cs @@ -183,22 +183,22 @@ public sealed class Haulables : ClassWithFishPrepatches public readonly HashSet ThingsQueuedToRemove = []; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CheckUpdate(List haulables) + public void CheckUpdate(ICollection haulables) { if (IsDirty(haulables)) Update(haulables); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List haulables) => haulables.Count != ContainedThings.Count; + public bool IsDirty(ICollection haulables) => haulables.Count != ContainedThings.Count; [MethodImpl(MethodImplOptions.NoInlining)] - public void Update(List haulables) + public void Update(ICollection haulables) { ContainedThings.Clear(); - for (var i = haulables.Count; i-- > 0;) - ContainedThings.Add(haulables[i].GetKey()); + foreach (var haulable in haulables) + ContainedThings.Add(haulable.GetKey()); } } } @@ -232,4 +232,4 @@ public static class ListerHaulablesExtensions if (cache.ContainedThings.Remove(t.GetKey())) haulables.Remove(t); } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Listers/Things.cs b/Source/PerformanceFish/Listers/Things.cs index 60fc1bb..968a7d7 100644 --- a/Source/PerformanceFish/Listers/Things.cs +++ b/Source/PerformanceFish/Listers/Things.cs @@ -568,9 +568,25 @@ public sealed class Things : ClassWithFishPatches public static unsafe IndexedFishSet InitializeThingsList() where T : Thing #pragma warning disable CS8622 - => new(static () => new(0, keyHashCodeByRefGetter: &GetKeyByRef, keyHashCodeGetter: &ThingHelper.GetKey)); + => new(static () => new(0, + keyHashCodeByRefGetter: &GetKeyByRef, + keyHashCodeGetter: &ThingHelper.GetKey, + keyEqualityByRefComparer: &EqualsByKeyByRef, + keyEqualityComparer: &EqualsByKey)); #pragma warning restore CS8622 private static int GetKeyByRef(ref T thing) where T : Thing => thing.GetKey(); + + private static bool EqualsByKey(T? left, T? right) where T : Thing + => ReferenceEquals(left, right) + || left is not null + && right is not null + && left.GetKey() == right.GetKey(); + + private static bool EqualsByKeyByRef(ref T? left, ref T? right) where T : Thing + => ReferenceEquals(left, right) + || left is not null + && right is not null + && left.GetKey() == right.GetKey(); } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/MiscOptimizations.cs b/Source/PerformanceFish/MiscOptimizations.cs index bde9c27..2b7287e 100644 --- a/Source/PerformanceFish/MiscOptimizations.cs +++ b/Source/PerformanceFish/MiscOptimizations.cs @@ -114,17 +114,30 @@ public sealed class MiscOptimizations : ClassWithFishPatches = "Throttles rotting on misconfigured defs to only recalculate every 256 ticks, instead of constantly."; public override MethodBase TargetMethodInfo { get; } +#if V1_6 + = AccessTools.DeclaredMethod(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTickInterval), + [typeof(int)])!; +#else = AccessTools.Method(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTick)); +#endif public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator) => Reflection.GetCodeInstructions(Replacement, generator); [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if V1_6 + public static void Replacement(RimWorld.CompRottable instance, int delta) + { + if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255)) + instance.TickInterval(256); + } +#else public static void Replacement(RimWorld.CompRottable instance) { if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255)) instance.Tick(256); } +#endif } public sealed class RitualObligationTrigger_Date : FirstPriorityFishPatch @@ -344,4 +357,4 @@ public sealed class MiscOptimizations : ClassWithFishPatches } #endif } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/ModsConfigPatches.cs b/Source/PerformanceFish/ModsConfigPatches.cs index b6c3ad8..f6d1432 100644 --- a/Source/PerformanceFish/ModsConfigPatches.cs +++ b/Source/PerformanceFish/ModsConfigPatches.cs @@ -17,7 +17,8 @@ public sealed class ModsConfigPatches : ClassWithFishPrepatches = "Fixes a MayRequire bug causing it to normally not recognize steam versions of mods if another local " + "copy exists."; - public override MethodBase TargetMethodBase { get; } = methodof(ModsConfig.AreAllActive); + public override MethodBase TargetMethodBase { get; } + = AccessTools.DeclaredMethod(typeof(ModsConfig), nameof(ModsConfig.AreAllActive), [typeof(string)])!; public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => IsActiveFix(ilProcessor, module); @@ -29,7 +30,9 @@ public sealed class ModsConfigPatches : ClassWithFishPrepatches = "Fixes a MayRequireAnyOf bug causing it to normally not recognize steam versions of mods if another " + "local copy exists, as well as its inability to strip whitespace."; - public override MethodBase TargetMethodBase { get; } = methodof(ModsConfig.IsAnyActiveOrEmpty); + public override MethodBase TargetMethodBase { get; } + = AccessTools.DeclaredMethod(typeof(ModsConfig), nameof(ModsConfig.IsAnyActiveOrEmpty), + [typeof(IEnumerable), typeof(bool)])!; public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => IsActiveFix(ilProcessor, module); @@ -59,4 +62,4 @@ public sealed class ModsConfigPatches : ClassWithFishPrepatches } public static bool IsActiveReplacement(string id) => ModLister.GetActiveModWithIdentifier(id, true) != null; -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/OnAssembliesLoaded.cs b/Source/PerformanceFish/OnAssembliesLoaded.cs index 7690c00..48d0b40 100644 --- a/Source/PerformanceFish/OnAssembliesLoaded.cs +++ b/Source/PerformanceFish/OnAssembliesLoaded.cs @@ -50,7 +50,9 @@ public static class OnAssembliesLoaded try { if (!patchClass.RequiresLoadedGameForPatching) + { patchClass.Patches.PatchAll(); + } } catch (Exception ex) { @@ -122,4 +124,4 @@ public static class OnAssembliesLoaded action.Method.FullDescription()}:\n{ex}"); } } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/PerformanceFish.sln b/Source/PerformanceFish/PerformanceFish.sln index d417805..d68e95d 100644 --- a/Source/PerformanceFish/PerformanceFish.sln +++ b/Source/PerformanceFish/PerformanceFish.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.1.31911.260 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.5", "1.5.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.6", "1.6.csproj", "{8B299603-8E75-4C35-AFC8-89748599E689}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.4", "1.4.csproj", "{0257ABCD-7009-4C7D-9BD8-599FC47595DB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6948F7E6-7ED7-4716-87EF-CFF1F465F561}" @@ -24,6 +26,10 @@ Global {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.Build.0 = Release|Any CPU + {8B299603-8E75-4C35-AFC8-89748599E689}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B299603-8E75-4C35-AFC8-89748599E689}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B299603-8E75-4C35-AFC8-89748599E689}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B299603-8E75-4C35-AFC8-89748599E689}.Release|Any CPU.Build.0 = Release|Any CPU {0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs b/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs index 906f3d8..0e7c1e4 100644 --- a/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs +++ b/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs @@ -15,6 +15,9 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches { public sealed class WorldObjectsHolderTickPatch : FishPrepatch { + private static FishTable _skippableCompTypes = null!; + private static FishTable _skippableWorldObjectTypes = null!; + public override string? Description { get; } = "The world objects holder is responsible for ticking every world object. This includes settlements, " + "caravans, outposts and any other object placed in the world, instead of maps. Normally it ticks " @@ -35,7 +38,11 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches var worldObjects = WorldObjectsHolder.tmpWorldObjects; for (var i = worldObjects.Count; i-- > 0;) +#if V1_6 + worldObjects[i].DoTick(); +#else worldObjects[i].Tick(); +#endif } public static void UpdateCache(WorldObjectsHolder instance) @@ -50,7 +57,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches var worldObject = instanceWorldObjects[i]; if (worldObject is not MapParent { HasMap: true } - && SkippableWorldObjects.Contains(worldObject.GetType()) + && CanSkipWorldObjectTick(worldObject) && (worldObject is not Settlement settlement || settlement.trader?.stock is null) && CanSkipCompTick(worldObject)) { @@ -64,6 +71,15 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches CachedMapsVersion = Current.gameInt.maps._version; } + private static bool CanSkipWorldObjectTick(WorldObject worldObject) + { +#if V1_6 + if (worldObject is IThingHolder) + return false; +#endif + return IsSkippableWorldObjectType(worldObject.GetType()); + } + private static bool CanSkipCompTick(WorldObject worldObject) { var comps = worldObject.comps; @@ -74,7 +90,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches if (comp is EnterCooldownComp { Active: true }) return false; - if (!SkippableComps.Contains(comp.GetType())) + if (!IsSkippableCompType(comp.GetType())) return false; comp.CompTick(); @@ -106,28 +122,69 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches public static void AddCompToWhiteList(Type compType) { _whitelistedTickingCompTypes = _whitelistedTickingCompTypes.Add(compType); - SkippableComps = InitializeSkippableComps(); + ResetSkippableCompTypes(); } public static void AddWorldObjectToWhiteList(Type worldObjectType) { _whitelistedWorldObjectTypes = _whitelistedWorldObjectTypes.Add(worldObjectType); - SkippableWorldObjects = InitializeSkippableWorldObjects(); + ResetSkippableWorldObjectTypes(); } - public static HashSet - SkippableComps = InitializeSkippableComps(), - SkippableWorldObjects = InitializeSkippableWorldObjects(); + private static void ResetSkippableCompTypes() + { + _skippableCompTypes = new() + { + ValueInitializer = static type => ComputeIsSkippableCompType(type) + }; + } - private static HashSet InitializeSkippableComps() - => MakeSubclassHashSet(typeof(WorldObjectComp), nameof(WorldObjectComp.CompTick), - _whitelistedTickingCompTypes); + private static void ResetSkippableWorldObjectTypes() + { + _skippableWorldObjectTypes = new() + { + ValueInitializer = static type => ComputeIsSkippableWorldObjectType(type) + }; + } - private static HashSet InitializeSkippableWorldObjects() - => MakeSubclassHashSet(typeof(WorldObject), nameof(WorldObject.Tick), _whitelistedWorldObjectTypes); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSkippableCompType(Type type) => _skippableCompTypes.GetOrAdd(type); - private static HashSet MakeSubclassHashSet(Type type, string name, Type?[] allowedDeclaringTypes) - => type.SubclassesWithNoMethodOverrideAndSelf(allowedDeclaringTypes, name).ToHashSet(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSkippableWorldObjectType(Type type) => _skippableWorldObjectTypes.GetOrAdd(type); + + private static bool ComputeIsSkippableCompType(Type type) + { + if (AccessTools.Method(type, nameof(WorldObjectComp.CompTick))?.DeclaringType is not { } declaringType + || !_whitelistedTickingCompTypes.Contains(declaringType)) + { + return false; + } + +#if V1_6 + return AccessTools.Method(type, nameof(WorldObjectComp.CompTickInterval), [typeof(int)])?.DeclaringType + is { } intervalDeclaringType + && _whitelistedTickingCompTypes.Contains(intervalDeclaringType); +#else + return true; +#endif + } + + private static bool ComputeIsSkippableWorldObjectType(Type type) + { + if (AccessTools.Method(type, "Tick")?.DeclaringType is not { } declaringType + || !_whitelistedWorldObjectTypes.Contains(declaringType)) + { + return false; + } + +#if V1_6 + return AccessTools.Method(type, "TickInterval", [typeof(int)])?.DeclaringType is { } intervalDeclaringType + && _whitelistedWorldObjectTypes.Contains(intervalDeclaringType); +#else + return true; +#endif + } public static bool CacheDirty(WorldObjectsHolder instance) => CachedWorldObjectsVersion != instance.worldObjects._version @@ -135,7 +192,12 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2; - static WorldObjectsHolderTickPatch() => Cache.Utility.Cleared += SetDirty; + static WorldObjectsHolderTickPatch() + { + ResetSkippableCompTypes(); + ResetSkippableWorldObjectTypes(); + Cache.Utility.Cleared += SetDirty; + } } public sealed class ExpandingIconCaching : FishPrepatch @@ -245,4 +307,4 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches } } } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/PrepatcherFields.cs b/Source/PerformanceFish/PrepatcherFields.cs index b00ffd4..dd19b40 100644 --- a/Source/PerformanceFish/PrepatcherFields.cs +++ b/Source/PerformanceFish/PrepatcherFields.cs @@ -9,6 +9,7 @@ using PerformanceFish.Hauling; using PerformanceFish.Planet; using Prepatcher; using RimWorld.Planet; +using PFMapEvents = PerformanceFish.Events.MapEvents; namespace PerformanceFish; @@ -22,7 +23,7 @@ public static class PrepatcherFields [PrepatcherField] [ValueInitializer(nameof(CreateMapEvents))] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static extern MapEvents.Instanced Events(this Map map); + public static extern PFMapEvents.Instanced Events(this Map map); [PrepatcherField] [ValueInitializer(nameof(CreateThingEvents))] @@ -175,7 +176,7 @@ public static class PrepatcherFields public static FishTable CreateIndexMapByGroup() => new(); public static Cache.CellGrid CreateItemCountGrid(Map map) => new(map); public static Cache.BitCellGrid CreateBitCellGrid(Map map) => new(map); - public static MapEvents.Instanced CreateMapEvents() => new(); + public static PFMapEvents.Instanced CreateMapEvents() => new(); public static ThingEvents.Instanced CreateThingEvents() => new(); public static StorageSettingsPatches.StorageSettingsCache CreateStorageSettingsCache() => new(); public static StorageDistrict[] GetDefaultDistrictArray() => StorageDistrict.GetDefaultArray(); @@ -290,4 +291,4 @@ public static class PrepatcherFields } // public static List CreateHediffCompList() => new(); -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Prepatching/FishPrepatch.cs b/Source/PerformanceFish/Prepatching/FishPrepatch.cs index be25dba..8c87d7a 100644 --- a/Source/PerformanceFish/Prepatching/FishPrepatch.cs +++ b/Source/PerformanceFish/Prepatching/FishPrepatch.cs @@ -4,6 +4,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Diagnostics; +using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; @@ -192,14 +193,26 @@ public abstract class FishPrepatch : FishPrepatchBase var stateParameter = postfixParameters.TryGetNamed("__state"); // Verify? It would make sense to match against prefix state - // var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool)); - var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod); + var hasReturnValue = targetMethod.ReturnType.FullName != module.TypeSystem.Void.FullName; + var resultVariable = hasReturnValue + ? patchVariables.Result ??= ilProcessor.DeclareLocal(targetMethod.ReturnType) + : GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod); var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter); + var retInstructions = instructions.Where(static instruction => instruction.OpCode == OpCodes.Ret).ToArray(); + if (retInstructions.Length == 0) + return; var postfixInstructions = new Collection(); - if (resultVariable != null) - postfixInstructions.Add((OpCodes.Stloc, resultVariable)); + if (hasReturnValue) + { + postfixInstructions.Add((OpCodes.Stloc, resultVariable!)); + postfixInstructions.Add(OpCodes.Nop); + } + else + { + postfixInstructions.Add(OpCodes.Nop); + } foreach (var parameter in postfixParameters) { @@ -217,36 +230,87 @@ public abstract class FishPrepatch : FishPrepatchBase postfixInstructions.Add((OpCodes.Call, postfixMethodInfo)); } - if (postfixMethodInfo.ReturnType == typeof(void) && resultVariable != null) - postfixInstructions.Add((OpCodes.Ldloc, resultVariable)); + if (postfixMethodInfo.ReturnType != typeof(void)) + postfixInstructions.Add((OpCodes.Stloc, resultVariable!)); - Span retInstructions = stackalloc int[instructions.Count]; - var retInstructionCount = 0; - - for (var i = 0; i < instructions.Count; i++) - { - if (instructions[i].OpCode == OpCodes.Ret) - retInstructions[retInstructionCount++] = i; - } + if (hasReturnValue) + postfixInstructions.Add((OpCodes.Ldloc, resultVariable!)); - var lastRetInstructionIndex = retInstructions[retInstructionCount - 1]; + var lastRetInstruction = retInstructions[^1]; + var lastRetInstructionIndex = instructions.IndexOf(lastRetInstruction); ilProcessor.InsertRange(lastRetInstructionIndex, postfixInstructions); + var captureStart = instructions[lastRetInstructionIndex]; + var postfixStart = instructions[lastRetInstructionIndex + (hasReturnValue ? 1 : 0)]; - for (var i = 0; i < instructions.Count; i++) + RetargetExceptionHandlerEnds(targetMethod.Body, lastRetInstruction, captureStart); + var retInstructionSet = retInstructions.ToHashSet(); + foreach (var instruction in instructions) { - var instruction = instructions[i]; - if (instruction.Operand is Instruction branchTarget && branchTarget.OpCode == OpCodes.Ret) - instruction.Operand = instructions[lastRetInstructionIndex]; + if (instruction.Operand is Instruction branchTarget && retInstructionSet.Contains(branchTarget)) + instruction.Operand = captureStart; } - for (var i = 0; i < retInstructionCount - 1; i++) + for (var i = 0; i < retInstructions.Length - 1; i++) { - var instruction = instructions[retInstructions[i]]; - instruction.OpCode = OpCodes.Br; - instruction.Operand = instructions[lastRetInstructionIndex]; + var instruction = retInstructions[i]; + if (hasReturnValue) + ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, resultVariable!)); + + instruction.OpCode = IsInProtectedRegion(targetMethod.Body, instruction) ? OpCodes.Leave : OpCodes.Br; + instruction.Operand = postfixStart; } } + private static void RetargetExceptionHandlerEnds(Mono.Cecil.Cil.MethodBody methodBody, Instruction oldBoundary, + Instruction newBoundary) + { + foreach (var exceptionHandler in methodBody.ExceptionHandlers) + { + if (exceptionHandler.TryEnd == oldBoundary) + exceptionHandler.TryEnd = newBoundary; + if (exceptionHandler.HandlerEnd == oldBoundary) + exceptionHandler.HandlerEnd = newBoundary; + } + } + + private static bool IsInProtectedRegion(Mono.Cecil.Cil.MethodBody methodBody, Instruction instruction) + { + var instructions = methodBody.Instructions; + var instructionIndex = instructions.IndexOf(instruction); + + foreach (var exceptionHandler in methodBody.ExceptionHandlers) + { + if (IsWithinRange(instructions, instructionIndex, exceptionHandler.TryStart, exceptionHandler.TryEnd) + || IsWithinRange(instructions, instructionIndex, exceptionHandler.HandlerStart, + exceptionHandler.HandlerEnd) + || exceptionHandler.FilterStart != null + && IsWithinRange(instructions, instructionIndex, exceptionHandler.FilterStart, + exceptionHandler.HandlerStart)) + { + return true; + } + } + + return false; + } + + private static bool IsWithinRange(Mono.Collections.Generic.Collection instructions, + int instructionIndex, Instruction? rangeStart, Instruction? rangeEnd) + { + if (rangeStart == null) + return false; + + var rangeStartIndex = instructions.IndexOf(rangeStart); + if (rangeStartIndex < 0 || instructionIndex < rangeStartIndex) + return false; + + if (rangeEnd == null) + return true; + + var rangeEndIndex = instructions.IndexOf(rangeEnd); + return rangeEndIndex < 0 || instructionIndex < rangeEndIndex; + } + private static VariableDefinition? GetStateVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables, ParameterInfo? stateParameter) => stateParameter != null @@ -352,4 +416,4 @@ public abstract class FishPrepatch : FishPrepatchBase { public VariableDefinition? Result, State; } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Rendering/DynamicDrawManagerPatches.cs b/Source/PerformanceFish/Rendering/DynamicDrawManagerPatches.cs index 934292d..269817c 100644 --- a/Source/PerformanceFish/Rendering/DynamicDrawManagerPatches.cs +++ b/Source/PerformanceFish/Rendering/DynamicDrawManagerPatches.cs @@ -68,40 +68,79 @@ public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches public static void CullAndInitializeThings(DynamicDrawManager instance) { var map = instance.map; - var mapSizeX = map.SizeX(); + var cellIndices = map.cellIndices; + var mapSizeX = GetMapSizeX(cellIndices); var fogGrid = map.fogGrid.fogGrid; var snowGrid = map.snowGrid.depthGrid; var checkShadows = MatBases.SunShadow.shader.isSupported; - var currentViewRect = Find.CameraDriver.CurrentViewRect.ClipInsideMap(map).ExpandedBy(1); + var currentViewRect = Find.CameraDriver.CurrentViewRect.ExpandedBy(1); +#if V1_6 + if (WorldComponent_GravshipController.GravshipRenderInProgess) + currentViewRect = currentViewRect.Encapsulate(WorldComponent_GravshipController.GravshipRenderBounds); +#endif + currentViewRect = currentViewRect.ClipInsideMap(map); var shadowViewRect = SectionLayer_SunShadows.GetSunShadowsViewRect(map, currentViewRect); instance.drawThings.UnwrapArray(out var drawThings, out var drawThingsCount); for (var i = 0; i < drawThingsCount; i++) { var drawThing = drawThings.UnsafeLoad(i); - var position = drawThing.Position; + var position = GetCullPosition(drawThing); var drawThingDef = drawThing.def; var cellIndex = position.CellToIndex(mapSizeX); - if ((!fogGrid[cellIndex] || drawThingDef.seeThroughFog) - && (drawThingDef.hideAtSnowDepth >= 1f - || drawThingDef.hideAtSnowDepth >= snowGrid[cellIndex])) + if (!CellIndicesContain(cellIndices, cellIndex) + || (!drawThingDef.seeThroughFog +#if V1_6 + && fogGrid.IsSet(cellIndex)) +#else + && fogGrid[cellIndex]) +#endif +#if V1_6 + || (drawThingDef.hideAtSnowOrSandDepth < 1f + && snowGrid[cellIndex] > drawThingDef.hideAtSnowOrSandDepth)) +#else + || (drawThingDef.hideAtSnowDepth < 1f + && snowGrid[cellIndex] > drawThingDef.hideAtSnowDepth)) +#endif { - if (currentViewRect.Contains(position) - || currentViewRect.Overlaps(drawThing.OccupiedDrawRect())) - { - ThingsToDraw.Add(drawThing); - - if (!DebugViewSettings.singleThreadedDrawing) - EnsureInitialized(drawThing); - } - else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(drawThing.Position)) - { - PawnShadowsToDraw.Add(pawn); - } + continue; + } + + if (currentViewRect.Contains(position) + || currentViewRect.Overlaps(drawThing.OccupiedDrawRect())) + { + ThingsToDraw.Add(drawThing); + + if (!DebugViewSettings.singleThreadedDrawing) + EnsureInitialized(drawThing); + } + else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(position)) + { + PawnShadowsToDraw.Add(pawn); } } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IntVec3 GetCullPosition(Thing drawThing) + => drawThing is Pawn ? drawThing.DrawPos.ToIntVec3() : drawThing.Position; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CellIndicesContain(CellIndices cellIndices, int index) +#if V1_6 + => cellIndices.Contains(index); +#else + => (uint)index < (uint)(cellIndices.mapSizeX * cellIndices.mapSizeZ); +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetMapSizeX(CellIndices cellIndices) +#if V1_6 + => cellIndices.SizeX; +#else + => cellIndices.mapSizeX; +#endif + public static void EnsureInitialized(Thing drawThing) { try @@ -228,4 +267,4 @@ public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches } } } -#endif \ No newline at end of file +#endif diff --git a/Source/PerformanceFish/RoomOptimizations.cs b/Source/PerformanceFish/RoomOptimizations.cs index 8246413..17b6037 100644 --- a/Source/PerformanceFish/RoomOptimizations.cs +++ b/Source/PerformanceFish/RoomOptimizations.cs @@ -349,8 +349,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All) { - var cellIndices = map.cellIndices; - if (((uint)c.x >= (uint)cellIndices.mapSizeX) | ((uint)c.z >= (uint)cellIndices.mapSizeZ)) + if (!c.InBounds(map)) return null; var regionAndRoomUpdater = map.regionAndRoomUpdater; @@ -360,8 +359,8 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription else if (regionAndRoomUpdater.AnythingToRebuild) LogIncorrectResultWarning(c); - var region = map.regionGrid.regionGrid[(c.z * cellIndices.mapSizeX) + c.x]; - return region is null || !region.valid || (region.type & allowedRegionTypes) == 0 + var region = map.regionGrid.GetValidRegionAt_NoRebuild(c); + return region is null || (region.type & allowedRegionTypes) == 0 ? null : region.District?.Room; } @@ -400,4 +399,4 @@ public sealed class RoomPrepatches : ClassWithFishPrepatches return tmpRegions; } } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/System/ReflectionCaching.cs b/Source/PerformanceFish/System/ReflectionCaching.cs index fe3665d..e92a7ec 100644 --- a/Source/PerformanceFish/System/ReflectionCaching.cs +++ b/Source/PerformanceFish/System/ReflectionCaching.cs @@ -510,44 +510,139 @@ public sealed class ReflectionCaching : ClassWithFishPatches public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit, out object[]? __result, out CustomAttributeState __state) { - var key = new CustomAttributeCache(obj, (__state.AttributeType = attributeType).TypeHandle, - __state.Inherit = inherit); + var key = new CustomAttributeCache(obj, attributeType.TypeHandle, inherit); + __state = new() { Key = key }; - ref var cache = ref CustomAttributeCache.GetOrAddReference(in key); - if (cache.Attributes is null) - TryGetFromCentralCache(ref key, ref cache); + if (TryGetCachedAttributes(ref key, out __result)) + return false; - return __state.State = (__result = cache.Attributes) is null; + MarkInProgress(ref key); + __state.ShouldStore = true; + __result = null; + return true; } [MethodImpl(MethodImplOptions.NoInlining)] - private static void TryGetFromCentralCache(ref CustomAttributeCache key, - ref CustomAttributeCacheValue cache) + private static bool TryGetCachedAttributes(ref CustomAttributeCache key, out object[]? result) { lock (_lock) - cache.Attributes = CustomAttributeCache.GetDirectly.GetOrAdd(ref key).Attributes; + { + if (TryUseCache(CustomAttributeCache.Get, ref key, out result)) + return true; + + if (TryUseCache(CustomAttributeCache.GetDirectly, ref key, out var centralResult)) + { + result = centralResult; + CustomAttributeCache.Get[key] = new() + { + Attributes = centralResult, + Status = CustomAttributeCacheStatus.Completed + }; + return true; + } + } + + result = null; + return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Postfix(ICustomAttributeProvider obj, object[]? __result, in CustomAttributeState __state) { - if (!__state.State || __result is null) + if (!__state.ShouldStore || __result is null) return; - UpdateCache(obj, __state, __result); + StoreCompleted(ref Unsafe.AsRef(in __state.Key), __result); } [MethodImpl(MethodImplOptions.NoInlining)] - private static void UpdateCache(ICustomAttributeProvider obj, in CustomAttributeState __state, - object[]? __result) + public static Exception? Finalizer(Exception? __exception, in CustomAttributeState __state) { - var key = new CustomAttributeCache(obj, __state.AttributeType.TypeHandle, __state.Inherit); - - CustomAttributeCache.GetExistingReference(in key).Attributes = __result; + if (__state.ShouldStore && __exception != null) + ClearInProgress(ref Unsafe.AsRef(in __state.Key)); + + return __exception; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TryUseCache(Dictionary cache, + ref CustomAttributeCache key, out object[]? result) + { + if (!cache.TryGetValue(key, out var value)) + { + result = null; + return false; + } + + switch (value.Status) + { + case CustomAttributeCacheStatus.Completed: + result = value.Attributes; + return true; + case CustomAttributeCacheStatus.InProgress: + LogInProgress(ref key); + result = null; + return false; + default: + result = null; + return false; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void MarkInProgress(ref CustomAttributeCache key) + { + var value = new CustomAttributeCacheValue + { + Status = CustomAttributeCacheStatus.InProgress + }; lock (_lock) - CustomAttributeCache.GetDirectly.GetReference(ref key).Attributes = __result; + { + CustomAttributeCache.Get[key] = value; + CustomAttributeCache.GetDirectly[key] = value; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void StoreCompleted(ref CustomAttributeCache key, object[] result) + { + var value = new CustomAttributeCacheValue + { + Attributes = result, + Status = CustomAttributeCacheStatus.Completed + }; + + lock (_lock) + { + CustomAttributeCache.Get[key] = value; + CustomAttributeCache.GetDirectly[key] = value; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ClearInProgress(ref CustomAttributeCache key) + { + lock (_lock) + { + if (CustomAttributeCache.Get.TryGetValue(key, out var localValue) + && localValue.Status == CustomAttributeCacheStatus.InProgress) + { + CustomAttributeCache.Get.Remove(key); + } + + if (CustomAttributeCache.GetDirectly.TryGetValue(key, out var centralValue) + && centralValue.Status == CustomAttributeCacheStatus.InProgress) + { + CustomAttributeCache.GetDirectly.Remove(key); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LogInProgress(ref CustomAttributeCache _) + { } private static object _lock = new(); @@ -556,12 +651,20 @@ public sealed class ReflectionCaching : ClassWithFishPatches public record struct CustomAttributeCacheValue { public object[]? Attributes; + public CustomAttributeCacheStatus Status; } public record struct CustomAttributeState { - public bool State, Inherit; - public Type AttributeType; + public bool ShouldStore; + public CustomAttributeCache Key; + } + + public enum CustomAttributeCacheStatus : byte + { + Uninitialized, + InProgress, + Completed } } @@ -752,7 +855,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches //} public override MethodBase TargetMethodInfo { get; } - = AccessTools.DeclaredMethod(typeof(MonoField), nameof(MonoField.GetValue))!; + = AccessTools.DeclaredMethod(AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo"), + nameof(FieldInfo.GetValue), [typeof(object)])!; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result) @@ -1181,4 +1285,4 @@ public sealed class ReflectionCaching : ClassWithFishPatches // populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and // MakeSetterDelegate } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/Utility/CellIndexExtensions.cs b/Source/PerformanceFish/Utility/CellIndexExtensions.cs index 517b656..185ebe5 100644 --- a/Source/PerformanceFish/Utility/CellIndexExtensions.cs +++ b/Source/PerformanceFish/Utility/CellIndexExtensions.cs @@ -12,7 +12,7 @@ public static class CellIndexExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CellToIndex(this in IntVec3 cell, CellIndices cellIndices) - => cell.CellToIndex(cellIndices.mapSizeX); + => cell.CellToIndex(GetMapSizeX(cellIndices)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CellToIndex(this in IntVec3 cell, int mapSizeX) => (cell.z * mapSizeX) + cell.x; @@ -22,7 +22,7 @@ public static class CellIndexExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CellToIndex(this IntVec2 cell, CellIndices cellIndices) - => cell.CellToIndex(cellIndices.mapSizeX); + => cell.CellToIndex(GetMapSizeX(cellIndices)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CellToIndex(this IntVec2 cell, int mapSizeX) => (cell.z * mapSizeX) + cell.x; @@ -31,7 +31,7 @@ public static class CellIndexExtensions public static int CellToIndex(int x, int z, Map map) => CellToIndex(x, z, map.cellIndices); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CellToIndex(int x, int z, CellIndices cellIndices) => CellToIndex(x, z, cellIndices.mapSizeX); + public static int CellToIndex(int x, int z, CellIndices cellIndices) => CellToIndex(x, z, GetMapSizeX(cellIndices)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CellToIndex(int x, int z, int mapSizeX) => (z * mapSizeX) + x; @@ -41,11 +41,24 @@ public static class CellIndexExtensions => thingGrid.thingGrid[cellIndex.Value]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsFogged(this FogGrid fogGrid, CellIndex cellIndex) => fogGrid.fogGrid[cellIndex.Value]; + public static bool IsFogged(this FogGrid fogGrid, CellIndex cellIndex) +#if V1_6 + => fogGrid.fogGrid.IsSet(cellIndex.Value); +#else + => fogGrid.fogGrid[cellIndex.Value]; +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetDepth(this SnowGrid snowGrid, CellIndex cellIndex) => snowGrid.depthGrid[cellIndex.Value]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetDepth(this SnowGrid snowGrid, int cellIndex) => snowGrid.depthGrid[cellIndex]; -} \ No newline at end of file + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetMapSizeX(CellIndices cellIndices) +#if V1_6 + => cellIndices.SizeX; +#else + => cellIndices.mapSizeX; +#endif +} diff --git a/Source/PerformanceFish/Utility/MiscExtensions.cs b/Source/PerformanceFish/Utility/MiscExtensions.cs index 1a8add6..04b894e 100644 --- a/Source/PerformanceFish/Utility/MiscExtensions.cs +++ b/Source/PerformanceFish/Utility/MiscExtensions.cs @@ -97,7 +97,7 @@ public static class MiscExtensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int SizeX(this Map map) => map.cellIndices.mapSizeX; + public static int SizeX(this Map map) => map.Size.x; public static Map? TryGetMapHeld(this Thing thing) { @@ -134,10 +134,18 @@ public static class MiscExtensions public static void InvokeWhenCellIndicesReady(this Map map, Action action) { +#if V1_6 + if (map.cellIndices.NumGridCells > 0) +#else if (map.cellIndices != null) +#endif + { action(map); + } else + { map.Events().ComponentsConstructed += action; + } } public static void AddLetterSilently(this LetterStack letterStack, Letter letter, Color color, bool playSound = false, @@ -164,4 +172,4 @@ public static class MiscExtensions letterStack.letters.Add(letter); letter.Received(); } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/UtilityPrepatches.cs b/Source/PerformanceFish/UtilityPrepatches.cs index 7387a39..360eac9 100644 --- a/Source/PerformanceFish/UtilityPrepatches.cs +++ b/Source/PerformanceFish/UtilityPrepatches.cs @@ -93,4 +93,4 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches instruction.Operand = null; } } -} \ No newline at end of file +}