Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified 1.6/Assemblies/VanillaPsycastsExpanded.dll
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
namespace VanillaPsycastsExpanded;

using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using Verse;


/// <summary>
/// The introduction of stacking focus types with complex focus strength calculations makes per-tick calculation of meditation focus
/// very computationally expensive. This can easily slow down games noticeably with even a single meditating pawn.
///
/// This class serves as a buffer, caching the boosted gain to a meditation focus from nearby foci, which massively speeds up
/// the function call for StatPart_NearbyFoci.TransformValue. Cached values expire after an hour, so in normal gameplay pawns will
/// never lose/gain more than a few percentage points of psyfocus compared to vanilla calculations, and only when meditation objects are
/// actively moved around a meditating pawn.
/// </summary>
public static class NearbyFocusBoostCachingUtility
{
private static int EXPIRATION_TIMEOUT = 2500; // 1 in-game hour

private class CachedFocusInfo : Tuple<Pawn, Thing, float, int>
{
//private bool usedSinceLastCalcuation = false;
private CachedFocusInfo(Pawn pawn, Thing focusTarget, float focus, int expiration) : base(pawn, focusTarget, focus, expiration) { }

public static CachedFocusInfo GenerateCacheInfoFor(Pawn pawn, Thing focusTarget, float baseVal)
{
float modifiedPsyfocus = baseVal;
var list = StatPart_NearbyFoci.AllFociNearby(focusTarget, pawn);
for (var i = 0; i < list.Count; i++) modifiedPsyfocus += list[i].value;


return new CachedFocusInfo(
pawn,
focusTarget,
modifiedPsyfocus,
Current.Game.tickManager.TicksGame + EXPIRATION_TIMEOUT
);
}

public Pawn Pawn { get { return this.Item1; } }
public Thing FocusTarget { get { return this.Item2; } }
public float ModifiedFocus { get { return this.Item3; } }
public int ExpirationTick { get { return this.Item4; } }

public bool IsExpired { get { return ExpirationTick < Current.Game.tickManager.TicksGame; } }
}

private static Dictionary<string, CachedFocusInfo> cachedInfo = new Dictionary<string, CachedFocusInfo>();

/** Tries to get the Modified psyfocus of a medititation foci for a given pawn, taking into account
* other nearby meditation foci that are valid for the pawn, and the foci's base psyfocus gain.
* If the value is cached and not stale, return that, otherwise recalculate the value and re-cache it
* before returning it.
*
*/
public static float GetOrCacheModifiedFociBoost(Pawn pawn, Thing focus, float basePsyfocus)
{
// Check if info is cached
CachedFocusInfo info;
string cacheKey = pawn.ThingID + focus.ThingID;
if (cachedInfo.ContainsKey(cacheKey))
{
info = cachedInfo[cacheKey];
// Check if info is still useable
if(!info.IsExpired)
{
return info.ModifiedFocus;
}
}
info = CachedFocusInfo.GenerateCacheInfoFor(pawn, focus, basePsyfocus);
cachedInfo[cacheKey] = info;

// Cleanup dictionary whenever we modify it to avoid caching dozens of visiting nobles' info forever
CleanupCache();

return info.ModifiedFocus;

}

private static void CleanupCache()
{
List<string> removeIds = new List<string>();
foreach (string id in cachedInfo.Keys) if (cachedInfo[id].IsExpired) removeIds.Add(id);
foreach (string id in removeIds)
{
//Log.Message($"Removing stale info for {id}");
cachedInfo.Remove(id);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@ public override void TransformValue(StatRequest req, ref float val)
if (req.Thing == null || req.Pawn == null || !ShouldApply || req.Thing.Map == null) return;
try
{

ShouldApply = false;
// Use foreach loop over Linq's Sum() to avoid the overhead invoking the delegate, which had significant effect on performance.
var list = AllFociNearby(req.Thing, req.Pawn);
for (var i = 0; i < list.Count; i++) val += list[i].value;
val = NearbyFocusBoostCachingUtility.GetOrCacheModifiedFociBoost(req.Pawn, req.Thing, val);
}
finally
{
ShouldApply = true;
}
}

private static List<(Thing thing, float value)> AllFociNearby(Thing main, Pawn pawn)
public static List<(Thing thing, float value)> AllFociNearby(Thing main, Pawn pawn)
{
var compMain = main.TryGetComp<CompMeditationFocus>();
if (compMain == null)
Expand All @@ -43,7 +42,7 @@ public override void TransformValue(StatRequest req, ref float val)
{
continue;
}

float strength = focus.parent.GetStatValueForPawn(StatDefOf.MeditationFocusStrength, pawn);
potentialFoci.Add((focus.parent, focus.Props.focusTypes, strength));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
<Compile Include="HarmonyPatches\Pawn_AbilityTracker_Notify_TemporaryAbilitiesChanged_Patch.cs" />
<Compile Include="HarmonyPatches\Pawn_GeneTracker_Notify_GenesChanged_Patch.cs" />
<Compile Include="HarmonyPatches\Pawn_HealthTracker_ShouldBeDeadFromLethalDamageThreshold_Patch.cs" />
<Compile Include="Meditation\NearbyFocusBoostCachingUtility.cs" />
<Compile Include="Misc\BackCompatabilityConverter_Psytrainers.cs" />
<Compile Include="Misc\BackCompatibilityConverter_Constructs.cs" />
<Compile Include="Misc\GenRadialCached.cs" />
Expand Down