// 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 PerformanceFish.Prepatching; /*using ThingRequestCache = PerformanceFish.Cache.ByReference;*/ namespace PerformanceFish; public sealed class ThingOwnerUtilityPreCaching : ClassWithFishPrepatches { public sealed class GetAllThingsRecursively_Patch : FishPrepatch { // https://github.com/jptrrs/HumanResources/blob/master/Source/Harmony/ThingOwnerUtility_GetAllThingsRecursively.cs // Harmony patches on generics like there strip out information from the generic T argument, due to harmony // replacing the method with one made with Reflection.Emit // noted down as known edge case on the harmony wiki here: // https://harmony.pardeike.net/articles/patching-edgecases.html#generics public override List IncompatibleModIDs { get; } = [ModCompatibility.PackageIDs.HUMAN_RESOURCES]; public override string Description { get; } = "Caching of information about mostly unspawned pawns and things. Relatively large performance impact"; public override MethodBase TargetMethodBase { get; } = methodof(ThingOwnerUtility.GetAllThingsRecursively); public static bool Prefix(Map map, ThingRequest request, List? outThings, bool allowUnreal, Predicate? passCheck, bool alsoGetSpawnedThings, out bool __state) where T : Thing { if (request.singleDef != null || request.group == 0 || !allowUnreal || passCheck != null || alsoGetSpawnedThings || outThings == null) { __state = false; return true; } var listsByGroup = map.listerThings.listsByGroup[(int)request.group]; if (listsByGroup is null) { __state = false; return true; } var key = new Cache.ByInt>(map.uniqueID, (byte)request.group); ref var cache = ref Cache.ByInt>.Get.GetOrAddReference(ref key); if (cache.Dirty || cache.ListsByGroupCount != listsByGroup.Count || cache.ThingHolderCount != ThingHolderCount(map)) { return __state = true; } outThings.ReplaceContentsWith(cache.Things!); return __state = false; } public static void Postfix(Map map, ThingRequest request, List outThings, bool __state) where T : Thing { if (!__state) return; ref var cache = ref Cache.ByInt>.Get.GetReference(new(map.uniqueID, (byte)request.group)); if (cache.Things is null) cache.Things = outThings.Copy(); else cache.Things.ReplaceContentsWith(outThings); cache.ListsByGroupCount = map.listerThings.listsByGroup[(int)request.group]?.Count ?? -1; cache.ThingHolderCount = ThingHolderCount(map); cache.SetDirty(false, map.uniqueID + (int)request.group + cache.ListsByGroupCount + cache.ThingHolderCount); } } public static int ThingHolderCount(Map map) => (map.passingShipManager?.passingShips.Count ?? 0) + map.components.Count + (map.listerThings.listsByGroup[(int)ThingRequestGroup.ThingHolder]?.Count ?? 0); } /*public sealed class ThingOwnerUtilityCaching : ClassWithFishPatches { public sealed class GetAllThingsRecursively_Patch : FishPatch { public override string Description { get; } = "Caching of information about mostly unspawned pawns and things. Relatively large performance impact"; public override Delegate TargetMethodGroup { get; } = ThingOwnerUtility.GetAllThingsRecursively; public static bool Prefix(Map map, ThingRequest request, IList? outThings, bool allowUnreal, Predicate? passCheck, bool alsoGetSpawnedThings, out bool __state) { if (request.singleDef != null || request.group == 0 || !allowUnreal || passCheck != null || alsoGetSpawnedThings || outThings == null) { __state = false; return true; } var listByGroup = map.listerThings.listsByGroup[(int)request.group]; if (listByGroup is null) { __state = false; return true; } var type = outThings.GetType().GetGenericArguments()[0]; var key = new ThingRequestCache(map.uniqueID, (byte)request.group, type.TypeHandle); ref var cache = ref ThingRequestCache.Get.GetOrAddReference(ref key); if (cache.Dirty || cache.count != listByGroup.Count || cache.thingHolderCount != ThingHolderCount(map)) { return __state = true; } outThings.Clear(); outThings.TryAddRange(cache.things!, type); return __state = false; } public static void Postfix(Map map, ThingRequest request, IList outThings, bool __state) { if (!__state) return; var type = outThings.GetType().GetGenericArguments()[0]; ref var cache = ref ThingRequestCache.Get.GetReference(new(map.uniqueID, (byte)request.group, type.TypeHandle)); if (cache.things is null) { cache.things = FisheryLib.CollectionExtensions.TryNew(outThings, type); } else { cache.things.Clear(); cache.things.TryAddRange(outThings, type); } cache.count = map.listerThings.listsByGroup[(int)request.group]?.Count ?? -1; cache.thingHolderCount = ThingHolderCount(map); cache.SetDirty(false, map.uniqueID + (int)request.group + cache.count + cache.thingHolderCount); } } public static int ThingHolderCount(Map map) => (map.passingShipManager?.passingShips.Count ?? 0) + map.components.Count + (map.listerThings.listsByGroup[(int)ThingRequestGroup.ThingHolder]?.Count ?? 0); }*/ /*public record struct ThingRequestInfo { private int _nextRefreshTick; public int count; public int thingHolderCount; public IList? things; public void SetDirty(bool value, int offset) => _nextRefreshTick = value ? 0 : TickHelper.Add(3072, offset, 2048); public bool Dirty => _nextRefreshTick < Current.Game.tickManager.TicksGame; }*/ public record struct ThingRequestInfo where T : Thing { private int _nextUpdateTick; public int ListsByGroupCount; public int ThingHolderCount; public List? Things; public void SetDirty(bool value, int offset) => _nextUpdateTick = value ? 0 : TickHelper.Add(3072, offset, 2048); public bool Dirty => TickHelper.Past(_nextUpdateTick); }