// 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 Mono.Cecil; using Mono.Cecil.Cil; using PerformanceFish.Prepatching; using RimWorld.Planet; namespace PerformanceFish.Planet; public sealed class WorldObjectsOptimization : ClassWithFishPrepatches { public sealed class WorldObjectsHolderTickPatch : FishPrepatch { public override bool DefaultState => true; private static List? _cachedWorldObjectsReference; private static List? _cachedSnapshotReference; private static int _cachedSnapshotVersion = -2; public override string? Description { get; } = "Reuses the world-object tick snapshot until the source list changes. Every object still receives DoTick."; public override MethodBase TargetMethodBase { get; } = AccessTools.Method(typeof(WorldObjectsHolder), nameof(WorldObjectsHolder.WorldObjectsHolderTick)); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(WorldObjectsHolderTick); public static void WorldObjectsHolderTick(WorldObjectsHolder instance) { if (CacheDirty(instance)) UpdateCache(instance); var worldObjects = WorldObjectsHolder.tmpWorldObjects; for (var i = 0; i < worldObjects.Count; i++) #if V1_6 worldObjects[i].DoTick(); #else worldObjects[i].Tick(); #endif } public static void UpdateCache(WorldObjectsHolder instance) { var instanceWorldObjects = instance.worldObjects; var snapshot = WorldObjectsHolder.tmpWorldObjects; snapshot.Clear(); snapshot.AddRange(instanceWorldObjects); CachedWorldObjectsVersion = instanceWorldObjects._version; _cachedWorldObjectsReference = instanceWorldObjects; _cachedSnapshotReference = snapshot; _cachedSnapshotVersion = snapshot._version; } public static int CachedWorldObjectsVersion = -2, CachedMapsVersion = -2; // Retained for mods that called the old whitelist API. The snapshot no longer skips any ticks. public static void AddCompToWhiteList(Type compType) => SetDirty(); public static void AddWorldObjectToWhiteList(Type worldObjectType) => SetDirty(); public static bool CacheDirty(WorldObjectsHolder instance) { var source = instance.worldObjects; var snapshot = WorldObjectsHolder.tmpWorldObjects; return !ReferenceEquals(_cachedWorldObjectsReference, source) || CachedWorldObjectsVersion != source._version || !ReferenceEquals(_cachedSnapshotReference, snapshot) || _cachedSnapshotVersion != snapshot._version || snapshot.Count != source.Count; } public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2; static WorldObjectsHolderTickPatch() { Cache.Utility.Cleared += SetDirty; } } public sealed class ExpandingIconCaching : FishPrepatch { public override string? Description { get; } = "Caches icons that get displayed for world objects like settlements on the planet view and adds various " + "safety checks to better catch and log errors in case of missing icons"; public override MethodBase TargetMethodBase { get; } = AccessTools.DeclaredPropertyGetter(typeof(WorldObject), nameof(WorldObject.ExpandingIcon)); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(ReplacementBody); public static Texture2D ReplacementBody(WorldObject __instance) => __instance.ExpandingIconCache() ?? InitializeExpandingIcon(__instance); [MethodImpl(MethodImplOptions.NoInlining)] public static Texture2D InitializeExpandingIcon(WorldObject? worldObject) { if (worldObject is null) return BaseContent.BadTex; var def = worldObject.def; ref var cache = ref worldObject.ExpandingIconCache(); try { cache = def?.ExpandingIconTexture; if (cache != null) return cache; var material = worldObject.Material; if (material != null) cache = material.mainTexture as Texture2D; if (cache == null) { cache = BaseContent.BadTex; Log.Error($"No expanding icon found for '{worldObject.ToStringSafe()}' of def '{ def?.ToStringSafe()}' from mod '{def?.modContentPack?.Name}'. Assigning default."); } return cache; } catch (Exception ex) { Log.Error($"Exception while initializing icon for '{worldObject.ToStringSafe()}' of def '{ def?.ToStringSafe()}' from mod '{def?.modContentPack?.Name}':\n{ex}"); return cache = BaseContent.BadTex; } } } public sealed class ExpandingIconColorCaching : FishPrepatch { public override string? Description { get; } = "Caches colors of icons that get displayed for world objects like settlements on the planet view and " + "adds various safety checks to better catch and log errors in case of missing data"; public override MethodBase TargetMethodBase { get; } = AccessTools.DeclaredPropertyGetter(typeof(WorldObject), nameof(WorldObject.ExpandingIconColor)); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(ReplacementBody); public static Color ReplacementBody(WorldObject __instance) => __instance.ExpandingIconColorCache() ?? InitializeExpandingIconColor(__instance); [MethodImpl(MethodImplOptions.NoInlining)] public static Color InitializeExpandingIconColor(WorldObject? worldObject) { if (worldObject is null) return Color.white; var def = worldObject.def; ref var cache = ref worldObject.ExpandingIconColorCache(); try { cache = def?.expandingIconColor; if (cache != null) return cache.GetValueOrDefault(); var material = worldObject.Material; if (material != null) cache = material.color; if (cache == null) { cache = Color.white; Log.Error($"No expanding icon color found for '{worldObject.ToStringSafe()}' of def '{ def?.ToStringSafe()}' from mod '{def?.modContentPack?.Name}'. Assigning default."); } return cache.GetValueOrDefault(); } catch (Exception ex) { Log.Error($"Exception while initializing icon color for '{worldObject.ToStringSafe()}' of def '{ def?.ToStringSafe()}' from mod '{def?.modContentPack?.Name}':\n{ex}"); return (cache = Color.white).GetValueOrDefault(); } } } }