1.6 to main #1

Merged
HRiggs merged 7 commits from rimworld-1.6 into main 2026-10-02 06:26:22 +00:00
20 changed files with 275 additions and 107 deletions
Showing only changes of commit 35187cf2d2 - Show all commits
+3
View File
@@ -481,3 +481,6 @@ $RECYCLE.BIN/
# Vim temporary swap files # Vim temporary swap files
*.swp *.swp
# Cache folders used in disk caching
Cache/*
Binary file not shown.
Binary file not shown.
@@ -56,8 +56,8 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
{ {
private int _defDatabaseVersion = -2; private int _defDatabaseVersion = -2;
private int _defRecipeUsersVersion = -2; private int _defRecipeUsersVersion = -2;
private RecipeDef _def; private RecipeDef? _def;
public List<ThingDef> RecipeUsers; public List<ThingDef> RecipeUsers = [];
public void Update(RecipeDef instance, IEnumerable<ThingDef> result) public void Update(RecipeDef instance, IEnumerable<ThingDef> result)
{ {
@@ -81,7 +81,7 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
=> _defDatabaseVersion != DefDatabase<ThingDef>.defsList._version => _defDatabaseVersion != DefDatabase<ThingDef>.defsList._version
|| _defRecipeUsersVersion != (_def.recipeUsers?._version ?? 0); || _defRecipeUsersVersion != (_def?.recipeUsers?._version ?? 0);
} }
} }
} }
+7 -35
View File
@@ -19,45 +19,17 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
+ "optimization, but yields accurate results instead of a placeholder value to avoid issues"; + "optimization, but yields accurate results instead of a placeholder value to avoid issues";
public override Delegate TargetMethodGroup { get; } = (Func<Thing, int>)GenLocalDate.DayTick; public override Delegate TargetMethodGroup { get; } = (Func<Thing, int>)GenLocalDate.DayTick;
public override int TranspilerMethodPriority => Priority.First; public override int PrefixMethodPriority => Priority.First;
public static CodeInstructions Transpiler(CodeInstructions codes, MethodBase method, ILGenerator generator) [MethodImpl(MethodImplOptions.AggressiveInlining)]
//=> Reflection.GetCodeInstructions(Replacement); public static bool Prefix(Thing thing, ref int __result)
{ {
var labelToStartOfMethod = generator.DefineLabel(); if (thing.TryGetMap() is not { } map)
codes.First().labels.Add(labelToStartOfMethod); return true;
yield return FishTranspiler.Argument(method, "thing"); __result = GenLocalDate.DayTick(map);
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState)); return false;
yield return FishTranspiler.Constant(0);
yield return FishTranspiler.IfLessThan(labelToStartOfMethod);
yield return FishTranspiler.Field(typeof(Current), nameof(Current.gameInt));
yield return FishTranspiler.Field(typeof(Game), nameof(Game.maps));
yield return FishTranspiler.Argument(method, "thing");
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
yield return FishTranspiler.Call(typeof(List<Map>), "get_Item");
yield return FishTranspiler.Call<Func<Map, int>>(GenLocalDate.DayTick);
yield return FishTranspiler.Return;
foreach (var code in codes)
yield return code;
} }
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static int Replacement(Thing thing)
// => thing.mapIndexOrState < 0 ? GenDate.DayTick(GenTicks.TicksAbs, GenLocalDate.LongitudeForDate(thing))
// : GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static bool Prefix(Thing thing, ref int __result)
//{
// if (thing.mapIndexOrState < 0)
// return true;
// __result = GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
// return false;
//}
} }
public sealed class DayTickByMap_Patch : FishPatch public sealed class DayTickByMap_Patch : FishPatch
@@ -98,10 +98,59 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
private static bool IsSkippableType(Type type) => _skippableTypes.GetOrAdd(type); private static bool IsSkippableType(Type type) => _skippableTypes.GetOrAdd(type);
private static bool ComputeIsSkippableType(Type type) private static bool ComputeIsSkippableType(Type type)
=> type == typeof(Gene) {
|| type.GetMethod(nameof(Gene.Tick), global::System.Reflection.BindingFlags.Instance if (type == typeof(Gene))
| global::System.Reflection.BindingFlags.Public return true;
| global::System.Reflection.BindingFlags.NonPublic
| global::System.Reflection.BindingFlags.DeclaredOnly, null, Type.EmptyTypes, null) == null; 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);
}
}
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);
}
}
} }
} }
@@ -3,7 +3,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at https://mozilla.org/MPL/2.0/.
global using LwmCompCache = PerformanceFish.Cache.ByReference<Verse.Thing, PerformanceFish.Hauling.LwmCompCacheValue>; global using LwmCompCache = PerformanceFish.Cache.ByReference<Verse.ThingDef, PerformanceFish.Hauling.LwmCompCacheValue>;
namespace PerformanceFish.Hauling; namespace PerformanceFish.Hauling;
@@ -11,9 +11,9 @@ public record struct LwmCompCacheValue
{ {
public CompProperties? CompProperties; public CompProperties? CompProperties;
public static CompProperties? TryGetLwmProps(Thing slotGroupParent) public static CompProperties? TryGetLwmProps(ThingDef thingDef)
=> ModCompatibility.Types.LWM.CompProperties != null => ModCompatibility.Types.LWM.CompProperties != null
? slotGroupParent.def.comps?.Find(static props ? thingDef.comps?.Find(static props
=> props.GetType() == ModCompatibility.Types.LWM.CompProperties) => props.GetType() == ModCompatibility.Types.LWM.CompProperties)
: null; : null;
@@ -4,7 +4,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/. // file, You can obtain one at https://mozilla.org/MPL/2.0/.
global using RimfactoryExtensionCache global using RimfactoryExtensionCache
= PerformanceFish.Cache.ByReference<Verse.Thing, PerformanceFish.Hauling.RimfactoryExtensionCacheValue>; = PerformanceFish.Cache.ByReference<Verse.ThingDef, PerformanceFish.Hauling.RimfactoryExtensionCacheValue>;
using RimfactoryTypes = PerformanceFish.ModCompatibility.Types.Rimfactory; using RimfactoryTypes = PerformanceFish.ModCompatibility.Types.Rimfactory;
namespace PerformanceFish.Hauling; namespace PerformanceFish.Hauling;
@@ -13,17 +13,17 @@ public record struct RimfactoryExtensionCacheValue
{ {
public DefModExtension? ModExtension; public DefModExtension? ModExtension;
public static DefModExtension? TryGetExtension(Thing slotGroupParent) public static DefModExtension? TryGetExtension(ThingDef thingDef)
=> RimfactoryTypes.ModExtension != null => RimfactoryTypes.ModExtension != null
? slotGroupParent.def.modExtensions?.Find(_extensionPredicate) ? thingDef.modExtensions?.Find(_extensionPredicate)
: null; : null;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasExtension(Thing slotGroupParent) public static bool HasExtension(Thing thing)
=> RimfactoryTypes.ModExtension != null && TestExtension(slotGroupParent); => RimfactoryTypes.ModExtension != null && TestExtension(thing.def);
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static bool TestExtension(Thing slotGroupParent) => slotGroupParent.TryGetRimfactoryExtension() != null; private static bool TestExtension(ThingDef thingDef) => TryGetExtension(thingDef) != null;
static RimfactoryExtensionCacheValue() => EnsureInitialized(); static RimfactoryExtensionCacheValue() => EnsureInitialized();
@@ -24,11 +24,11 @@ public static class StorageExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CompProperties? TryGetLwmCompProperties(this Thing thing) public static CompProperties? TryGetLwmCompProperties(this Thing thing)
=> LwmCompCache.GetOrAdd(thing).CompProperties; => LwmCompCache.GetOrAdd(thing.def).CompProperties;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DefModExtension? TryGetRimfactoryExtension(this Thing thing) public static DefModExtension? TryGetRimfactoryExtension(this Thing thing)
=> RimfactoryExtensionCache.GetOrAdd(thing).ModExtension; => RimfactoryExtensionCache.GetOrAdd(thing.def).ModExtension;
public static int GetTotalSlots(this SlotGroup slotGroup) => slotGroup.GetTotalSlots(slotGroup.CellsList.Count); public static int GetTotalSlots(this SlotGroup slotGroup) => slotGroup.GetTotalSlots(slotGroup.CellsList.Count);
@@ -3,6 +3,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Linq;
using PerformanceFish.ModCompatibility; using PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem; namespace PerformanceFish.JobSystem;
@@ -41,11 +42,19 @@ public sealed class JobGiver_HaulOptimization : ClassWithFishPatches
} }
} }
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn) public static ICollection<Thing> SortedThingsPotentiallyNeedingHauling(ICollection<Thing> things, Pawn pawn)
{ {
((ThingPositionComparer)_comparer.Target).rootCell = pawn.Position; ((ThingPositionComparer)_comparer.Target).rootCell = pawn.Position;
things.Sort(_comparer);
return things; if (things is List<Thing> list)
{
list.Sort(_comparer);
return list;
}
var sortedThings = things.ToList();
sortedThings.Sort(_comparer);
return sortedThings;
} }
private static Comparison<Thing> _comparer = new ThingPositionComparer().Compare; private static Comparison<Thing> _comparer = new ThingPositionComparer().Compare;
+8
View File
@@ -12,6 +12,9 @@ namespace PerformanceFish.Listers;
public sealed class ThingsPrepatches : ClassWithFishPrepatches public sealed class ThingsPrepatches : ClassWithFishPrepatches
{ {
private static readonly AccessTools.FieldRef<ListerThings, List<IHaulSource>> _haulSources
= AccessTools.FieldRefAccess<ListerThings, List<IHaulSource>>("haulSources");
public sealed class AddPatch : FishPrepatch public sealed class AddPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
@@ -31,6 +34,8 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
lock (__instance.LockObject()) lock (__instance.LockObject())
{ {
AddToDefList(__instance, t); AddToDefList(__instance, t);
if (t is IHaulSource haulSource)
_haulSources(__instance).Add(haulSource);
var allGroups = ThingListGroupHelper.AllGroups; var allGroups = ThingListGroupHelper.AllGroups;
foreach (var thingRequestGroup in allGroups) foreach (var thingRequestGroup in allGroups)
@@ -74,6 +79,8 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
lock (__instance.LockObject()) lock (__instance.LockObject())
{ {
RemoveFromDefList(__instance, t); RemoveFromDefList(__instance, t);
if (t is IHaulSource haulSource)
_haulSources(__instance).Remove(haulSource);
var allGroups = ThingListGroupHelper.AllGroups; var allGroups = ThingListGroupHelper.AllGroups;
for (var i = 0; i < allGroups.Length; i++) for (var i = 0; i < allGroups.Length; i++)
@@ -268,6 +275,7 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
__instance.IndexMapByDef().Clear(); __instance.IndexMapByDef().Clear();
__instance.IndexMapByGroup().Clear(); __instance.IndexMapByGroup().Clear();
_haulSources(__instance).Clear();
ClearTypeLists(__instance); ClearTypeLists(__instance);
} }
+4 -3
View File
@@ -59,15 +59,16 @@ public sealed class LogPatches : ClassWithFishPrepatches
public static void InsertEarlyMessages() public static void InsertEarlyMessages()
{ {
var unhandledMessages = Log.UnhandledMessages; var unhandledMessages = Log.UnhandledMessages;
if (unhandledMessages is null)
Debug.LogError("Message queue is null");
Log.Ready = true; Log.Ready = true;
Verse.Log.messageQueue.Enqueue(new(LogMessageType.Message, PERFORMANCE_FISH_WELCOME_MESSAGE, Verse.Log.messageQueue.Enqueue(new(LogMessageType.Message, PERFORMANCE_FISH_WELCOME_MESSAGE,
PERFORMANCE_FISH_WELCOME_STACKTRACE)); PERFORMANCE_FISH_WELCOME_STACKTRACE));
while (unhandledMessages!.TryDequeue(out var message)) if (unhandledMessages is null)
return;
while (unhandledMessages.TryDequeue(out var message))
{ {
try try
{ {
+4 -2
View File
@@ -193,8 +193,6 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
Patched = true; Patched = true;
foreach (var method in TargetMethodInfos) foreach (var method in TargetMethodInfos)
{ {
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
try try
{ {
if (IsBenchmarking) if (IsBenchmarking)
@@ -232,6 +230,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority))); finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
@@ -291,6 +290,9 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null) private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
=> GetType().TryGetMethod(nameCaseInsensitive, predicate); => GetType().TryGetMethod(nameCaseInsensitive, predicate);
private static string GetTargetMethodName(MethodBase method)
=> $"{method.DeclaringType?.FullName ?? "<no-type>"}.{method.Name}";
private static int TryGetPriority(MethodInfo? info) private static int TryGetPriority(MethodInfo? info)
=> info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal; => info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
@@ -17,6 +17,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
{ {
private static FishTable<Type, bool> _skippableCompTypes = null!; private static FishTable<Type, bool> _skippableCompTypes = null!;
private static FishTable<Type, bool> _skippableWorldObjectTypes = null!; private static FishTable<Type, bool> _skippableWorldObjectTypes = null!;
private static readonly List<IThingHolder> _tmpThingHolders = [];
public override string? Description { get; } public override string? Description { get; }
= "The world objects holder is responsible for ticking every world object. This includes settlements, " = "The world objects holder is responsible for ticking every world object. This includes settlements, "
@@ -73,16 +74,44 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
private static bool CanSkipWorldObjectTick(WorldObject worldObject) private static bool CanSkipWorldObjectTick(WorldObject worldObject)
{ {
#if V1_6 return IsSkippableWorldObjectType(worldObject.GetType()) && !HasTickableContents(worldObject);
if (worldObject is IThingHolder) }
private static bool HasTickableContents(WorldObject worldObject)
{
if (worldObject is not IThingHolder holder)
return false; return false;
#endif
return IsSkippableWorldObjectType(worldObject.GetType()); lock (_tmpThingHolders)
{
_tmpThingHolders.Clear();
_tmpThingHolders.Add(holder);
holder.GetChildHolders(_tmpThingHolders);
for (var i = 0; i < _tmpThingHolders.Count; i++)
{
var thingHolder = _tmpThingHolders[i];
if (thingHolder is IThingHolderTickable tickable && !tickable.ShouldTickContents)
continue;
var directlyHeldThings = thingHolder.GetDirectlyHeldThings();
if (directlyHeldThings?.Owner is not null and not Map and not Caravan)
{
_tmpThingHolders.Clear();
return true;
}
}
_tmpThingHolders.Clear();
return false;
}
} }
private static bool CanSkipCompTick(WorldObject worldObject) private static bool CanSkipCompTick(WorldObject worldObject)
{ {
var comps = worldObject.comps; var comps = worldObject.comps;
if (comps.Count == 0)
return true;
for (var i = comps.Count; i-- > 0;) for (var i = comps.Count; i-- > 0;)
{ {
@@ -92,8 +121,6 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
if (!IsSkippableCompType(comp.GetType())) if (!IsSkippableCompType(comp.GetType()))
return false; return false;
comp.CompTick();
} }
return true; return true;
@@ -106,10 +133,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
private static Type?[] private static Type?[]
_whitelistedTickingCompTypes = _whitelistedTickingCompTypes =
[ [
typeof(WorldObjectComp), typeof(WorldObjectComp)
typeof(FormCaravanComp),
typeof(TimedDetectionRaids),
typeof(EnterCooldownComp)
], ],
_whitelistedWorldObjectTypes = _whitelistedWorldObjectTypes =
[ [
@@ -13,8 +13,9 @@ public static class FishPrepatchExtensions
{ {
var prepatches = ToSortedList(allPrepatchClasses, true); var prepatches = ToSortedList(allPrepatchClasses, true);
foreach (var prepatch in prepatches) for (var i = 0; i < prepatches.Count; i++)
{ {
var prepatch = prepatches[i];
if (!prepatch.Enabled) if (!prepatch.Enabled)
continue; continue;
@@ -40,8 +41,9 @@ public static class FishPrepatchExtensions
{ {
var prepatches = ToSortedList(allPrepatchClasses, false); var prepatches = ToSortedList(allPrepatchClasses, false);
foreach (var prepatch in prepatches) for (var i = 0; i < prepatches.Count; i++)
{ {
var prepatch = prepatches[i];
if (!prepatch.IsActive) if (!prepatch.IsActive)
continue; continue;
@@ -70,4 +72,5 @@ public static class FishPrepatchExtensions
prepatches.Sort(FishPrepatchBase.PriorityComparer); prepatches.Sort(FishPrepatchBase.PriorityComparer);
return prepatches; return prepatches;
} }
} }
@@ -0,0 +1,50 @@
// 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/.
namespace PerformanceFish.Rendering;
public sealed class TextureAtlasPatches : ClassWithFishPatches
{
public sealed class StaticTextureAtlas_ApplyTextureCompression : FishPatch
{
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 "
+ "crashes in debug Mono builds.";
public override MethodBase TargetMethodInfo
=> AccessTools.DeclaredMethod(typeof(StaticTextureAtlas), "ApplyTextureCompression");
public static bool Prefix(StaticTextureAtlas __instance, bool noGpuCompressionSupport)
{
if (!noGpuCompressionSupport || !SystemInfo.supportsComputeShaders)
return true;
if (__instance.ColorTexture != null)
{
var name = __instance.ColorTexture.name;
var compressed = StaticTextureAtlas.FastCompressDXT(__instance.ColorTexture, deleteOriginal: true);
compressed.name = name;
_colorTextureField.SetValue(__instance, compressed);
}
if (__instance.MaskTexture != null)
{
var name = __instance.MaskTexture.name;
var compressed = StaticTextureAtlas.FastCompressDXT(__instance.MaskTexture, deleteOriginal: true);
compressed.name = name;
_maskTextureField.SetValue(__instance, compressed);
}
return false;
}
private static readonly FieldInfo _colorTextureField
= AccessTools.DeclaredField(typeof(StaticTextureAtlas), "colorTexture");
private static readonly FieldInfo _maskTextureField
= AccessTools.DeclaredField(typeof(StaticTextureAtlas), "maskTexture");
}
}
@@ -93,7 +93,7 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
var data = file.ReadAllBytes(); var data = file.ReadAllBytes();
texture2D = new(2, 2, TextureFormat.Alpha8, true); texture2D = new(2, 2, TextureFormat.Alpha8, true);
texture2D.LoadImage(data); texture2D.LoadImage(data);
FixMipMapsIfNeeded(texture2D, data, file); FixMipMapsIfNeeded(ref texture2D, data, file);
} }
if (!isDds && Prefs.TextureCompression) if (!isDds && Prefs.TextureCompression)
@@ -120,11 +120,11 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
var texture2D = new Texture2D(2, 2, TextureFormat.Alpha8, true); var texture2D = new Texture2D(2, 2, TextureFormat.Alpha8, true);
texture2D.LoadImage(data); texture2D.LoadImage(data);
FixMipMapsIfNeeded(texture2D, data, file); FixMipMapsIfNeeded(ref texture2D, data, file);
return texture2D; return texture2D;
} }
public static void FixMipMapsIfNeeded(Texture2D texture2D, byte[] data, VirtualFile file) public static void FixMipMapsIfNeeded(ref Texture2D texture2D, byte[] data, VirtualFile file)
{ {
if (((texture2D.width & 3) == 0) & ((texture2D.height & 3) == 0)) if (((texture2D.width & 3) == 0) & ((texture2D.height & 3) == 0))
return; return;
@@ -10,6 +10,7 @@ using System.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using PerformanceFish.Cache; using PerformanceFish.Cache;
using PerformanceFish.ModCompatibility; using PerformanceFish.ModCompatibility;
#if false
using ConstructorInfoCache using ConstructorInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags,
PerformanceFish.System.ReflectionCaching.RecordArray<System.Type>, PerformanceFish.System.ReflectionCaching.RecordArray<System.Type>,
@@ -56,11 +57,13 @@ using ActivatorCache
using CustomAttributeCache using CustomAttributeCache
= PerformanceFish.Cache.ByReferenceClassic<System.Reflection.ICustomAttributeProvider, System.RuntimeTypeHandle, bool, = PerformanceFish.Cache.ByReferenceClassic<System.Reflection.ICustomAttributeProvider, System.RuntimeTypeHandle, bool,
PerformanceFish.System.ReflectionCaching.MonoCustomAttrs.CustomAttributeCacheValue>; PerformanceFish.System.ReflectionCaching.MonoCustomAttrs.CustomAttributeCacheValue>;
#endif
namespace PerformanceFish.System; namespace PerformanceFish.System;
public sealed class ReflectionCaching : ClassWithFishPatches public sealed class ReflectionCaching : ClassWithFishPatches
{ {
#if false
static ReflectionCaching() // necessary to prevent recursion between patches and function pointer creation static ReflectionCaching() // necessary to prevent recursion between patches and function pointer creation
{ {
try try
@@ -86,6 +89,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
ex}"); ex}");
} }
} }
#endif
public record struct StateAndFlags public record struct StateAndFlags
{ {
@@ -93,6 +97,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
public BindingFlags Flags; public BindingFlags Flags;
} }
#if false
public sealed class TypePatches public sealed class TypePatches
{ {
public sealed class GetField_Patch : FishPatch public sealed class GetField_Patch : FishPatch
@@ -513,9 +518,17 @@ public sealed class ReflectionCaching : ClassWithFishPatches
var key = new CustomAttributeCache(obj, attributeType.TypeHandle, inherit); var key = new CustomAttributeCache(obj, attributeType.TypeHandle, inherit);
__state = new() { Key = key }; __state = new() { Key = key };
if (TryGetCachedAttributes(ref key, out __result)) var cacheLookup = TryGetCachedAttributes(ref key, out __result);
if (cacheLookup == CustomAttributeCacheLookup.Hit)
return false; return false;
if (cacheLookup == CustomAttributeCacheLookup.InProgress)
{
__result = null;
return true;
}
MarkInProgress(ref key); MarkInProgress(ref key);
__state.ShouldStore = true; __state.ShouldStore = true;
__result = null; __result = null;
@@ -523,14 +536,17 @@ public sealed class ReflectionCaching : ClassWithFishPatches
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryGetCachedAttributes(ref CustomAttributeCache key, out object[]? result) private static CustomAttributeCacheLookup TryGetCachedAttributes(ref CustomAttributeCache key,
out object[]? result)
{ {
lock (_lock) lock (_lock)
{ {
if (TryUseCache(CustomAttributeCache.Get, ref key, out result)) var localLookup = TryUseCache(CustomAttributeCache.Get, ref key, out result);
return true; if (localLookup != CustomAttributeCacheLookup.Miss)
return localLookup;
if (TryUseCache(CustomAttributeCache.GetDirectly, ref key, out var centralResult)) var centralLookup = TryUseCache(CustomAttributeCache.GetDirectly, ref key, out var centralResult);
if (centralLookup == CustomAttributeCacheLookup.Hit)
{ {
result = centralResult; result = centralResult;
CustomAttributeCache.Get[key] = new() CustomAttributeCache.Get[key] = new()
@@ -538,12 +554,15 @@ public sealed class ReflectionCaching : ClassWithFishPatches
Attributes = centralResult, Attributes = centralResult,
Status = CustomAttributeCacheStatus.Completed Status = CustomAttributeCacheStatus.Completed
}; };
return true; return CustomAttributeCacheLookup.Hit;
} }
if (centralLookup == CustomAttributeCacheLookup.InProgress)
return CustomAttributeCacheLookup.InProgress;
} }
result = null; result = null;
return false; return CustomAttributeCacheLookup.Miss;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -566,27 +585,28 @@ public sealed class ReflectionCaching : ClassWithFishPatches
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryUseCache(Dictionary<CustomAttributeCache, CustomAttributeCacheValue> cache, private static CustomAttributeCacheLookup TryUseCache(
Dictionary<CustomAttributeCache, CustomAttributeCacheValue> cache,
ref CustomAttributeCache key, out object[]? result) ref CustomAttributeCache key, out object[]? result)
{ {
if (!cache.TryGetValue(key, out var value)) if (!cache.TryGetValue(key, out var value))
{ {
result = null; result = null;
return false; return CustomAttributeCacheLookup.Miss;
} }
switch (value.Status) switch (value.Status)
{ {
case CustomAttributeCacheStatus.Completed: case CustomAttributeCacheStatus.Completed:
result = value.Attributes; result = value.Attributes;
return true; return CustomAttributeCacheLookup.Hit;
case CustomAttributeCacheStatus.InProgress: case CustomAttributeCacheStatus.InProgress:
LogInProgress(ref key); LogInProgress(ref key);
result = null; result = null;
return false; return CustomAttributeCacheLookup.InProgress;
default: default:
result = null; result = null;
return false; return CustomAttributeCacheLookup.Miss;
} }
} }
@@ -666,6 +686,13 @@ public sealed class ReflectionCaching : ClassWithFishPatches
InProgress, InProgress,
Completed Completed
} }
private enum CustomAttributeCacheLookup : byte
{
Miss,
Hit,
InProgress
}
} }
public record struct RecordArray<T> public record struct RecordArray<T>
@@ -854,9 +881,11 @@ public sealed class ReflectionCaching : ClassWithFishPatches
// Log.Message(types.ToStringSafeEnumerable()); // Log.Message(types.ToStringSafeEnumerable());
//} //}
public override MethodBase TargetMethodInfo { get; } public override MethodBase? TargetMethodInfo { get; }
= AccessTools.DeclaredMethod(AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo"), = AccessTools.DeclaredMethod(
nameof(FieldInfo.GetValue), [typeof(object)])!; AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo")
?? AccessTools.TypeByName("System.Reflection.MonoField"),
nameof(FieldInfo.GetValue), [typeof(object)]);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result) public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
@@ -1285,4 +1314,5 @@ public sealed class ReflectionCaching : ClassWithFishPatches
// populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and // populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and
// MakeSetterDelegate // MakeSetterDelegate
} }
#endif
} }
@@ -101,7 +101,10 @@ public static class MiscExtensions
public static Map? TryGetMapHeld(this Thing thing) public static Map? TryGetMapHeld(this Thing thing)
{ {
var maps = Current.Game.Maps; var maps = Find.Maps;
if (maps == null)
return thing.ParentHolder is { } rootHolder ? ThingOwnerUtility.GetRootMap(rootHolder) : null;
var mapIndex = (uint)thing.mapIndexOrState; var mapIndex = (uint)thing.mapIndexOrState;
return mapIndex < (uint)maps.Count return mapIndex < (uint)maps.Count
@@ -114,7 +117,10 @@ public static class MiscExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Map? TryGetMap(this Thing thing) public static Map? TryGetMap(this Thing thing)
{ {
var maps = Current.Game.Maps; var maps = Find.Maps;
if (maps == null)
return null;
var mapIndex = (uint)thing.mapIndexOrState; var mapIndex = (uint)thing.mapIndexOrState;
return mapIndex < (uint)maps.Count ? maps[(int)mapIndex] : null; return mapIndex < (uint)maps.Count ? maps[(int)mapIndex] : null;
@@ -127,7 +133,18 @@ public static class MiscExtensions
public static Map GetMap(this Region region) => Current.Game.Maps[region.mapIndex]; public static Map GetMap(this Region region) => Current.Game.Maps[region.mapIndex];
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsSpawned(this Thing thing) => (uint)thing.mapIndexOrState < (uint)Current.Game.Maps.Count; public static bool IsSpawned(this Thing thing)
{
var maps = Find.Maps;
if (thing.mapIndexOrState < 0 || maps == null)
return false;
if (thing.mapIndexOrState < maps.Count)
return true;
Verse.Log.ErrorOnce($"Thing {thing.ThingID} is associated with invalid map index {thing.mapIndexOrState}", 64664487);
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetLoadID(this Bill bill) => bill.loadID; public static int GetLoadID(this Bill bill) => bill.loadID;