118 lines
3.9 KiB
C#
118 lines
3.9 KiB
C#
// 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 System.Linq;
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using PerformanceFish.Prepatching;
|
|
using RimWorld.Planet;
|
|
|
|
namespace PerformanceFish.Planet;
|
|
|
|
public class WorldObjectsOptimization : ClassWithFishPrepatches
|
|
{
|
|
public class WorldObjectsHolderTickPatch : FishPrepatch
|
|
{
|
|
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 "
|
|
+ "everything equally, including many static objects that cannot possibly affect anything from a tick. "
|
|
+ "This patch improves the world objects holder to determine objects 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(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 = worldObjects.Count; i-- > 0;)
|
|
worldObjects[i].Tick();
|
|
}
|
|
|
|
public static void UpdateCache(WorldObjectsHolder instance)
|
|
{
|
|
var staticWorldObjects = WorldObjectsHolder.tmpWorldObjects;
|
|
staticWorldObjects.Clear();
|
|
|
|
var instanceWorldObjects = instance.worldObjects;
|
|
|
|
for (var i = instanceWorldObjects.Count; i-- > 0;)
|
|
{
|
|
var worldObject = instanceWorldObjects[i];
|
|
|
|
if (worldObject is not MapParent { HasMap: true }
|
|
&& SkippableWorldObjects.Contains(worldObject.GetType())
|
|
&& (worldObject is not Settlement settlement || settlement.trader?.stock is null)
|
|
&& CanSkipCompTick(worldObject))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
staticWorldObjects.Add(worldObject);
|
|
}
|
|
|
|
CachedWorldObjectsVersion = instanceWorldObjects._version;
|
|
CachedMapsVersion = Current.gameInt.maps._version;
|
|
}
|
|
|
|
private static bool CanSkipCompTick(WorldObject worldObject)
|
|
{
|
|
var comps = worldObject.comps;
|
|
|
|
for (var i = comps.Count; i-- > 0;)
|
|
{
|
|
var comp = comps[i];
|
|
if (comp is EnterCooldownComp { Active: true })
|
|
return false;
|
|
|
|
if (!SkippableComps.Contains(comp.GetType()))
|
|
return false;
|
|
|
|
comp.CompTick();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static int
|
|
CachedWorldObjectsVersion = -2,
|
|
CachedMapsVersion = -2;
|
|
|
|
private static Type[]
|
|
_whitelistedTickingCompTypes
|
|
=
|
|
{
|
|
typeof(WorldObjectComp),
|
|
typeof(FormCaravanComp),
|
|
typeof(TimedDetectionRaids),
|
|
typeof(EnterCooldownComp)
|
|
},
|
|
_whitelistedWorldObjectTypes = { typeof(WorldObject), typeof(MapParent), typeof(Settlement) };
|
|
|
|
public static HashSet<Type>
|
|
SkippableComps = MakeSubclassHashSet(typeof(WorldObjectComp), nameof(WorldObjectComp.CompTick),
|
|
_whitelistedTickingCompTypes),
|
|
SkippableWorldObjects = MakeSubclassHashSet(typeof(WorldObject), nameof(WorldObject.Tick),
|
|
_whitelistedWorldObjectTypes);
|
|
|
|
private static HashSet<Type> MakeSubclassHashSet(Type type, string name, Type[] allowedDeclaringTypes)
|
|
=> type.SubclassesWithNoMethodOverrideAndSelf(allowedDeclaringTypes, name).ToHashSet();
|
|
|
|
public static bool CacheDirty(WorldObjectsHolder instance)
|
|
=> CachedWorldObjectsVersion != instance.worldObjects._version
|
|
|| CachedMapsVersion != Current.gameInt.maps._version;
|
|
|
|
public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2;
|
|
|
|
static WorldObjectsHolderTickPatch() => Cache.Utility.Cleared += SetDirty;
|
|
}
|
|
} |