Lots of new optimizations utilizing prepatcher

To start, this mod now requires Prepatcher and Fishery.
New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
bbradson
2023-06-03 20:24:38 +02:00
parent cb0a407425
commit ca9321add8
125 changed files with 11272 additions and 3681 deletions
@@ -58,8 +58,30 @@
</Choose>
<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" />
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
<Publicize Include="Assembly-CSharp:RimWorld.JobGiver_Haul.TryGiveJob" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute" />
<DoNotPublicize Include="0Harmony:Mono.Collections.Generic.Collection`1" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.buckets" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.entries" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.version" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2+Entry" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._items" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._version" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._size" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1.EnsureCapacity" />
<Publicize Include="System.Core:System.Collections.Generic.HashSet`1._version" />
<Publicize Include="mscorlib:System.MonoCustomAttrs.GetCustomAttributes" />
<Publicize Include="mscorlib:System.RuntimeType" />
<Publicize Include="mscorlib:System.Reflection.MonoField" />
<Publicize Include="mscorlib:System.String.m_firstChar" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.analyzers" Version="1.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
+435 -16
View File
@@ -2,34 +2,242 @@ global using System;
global using System.Collections.Generic;
global using Xunit;
global using Xunit.Abstractions;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using FisheryLib;
using HarmonyLib;
using JetBrains.Annotations;
using PerformanceFish.Collections;
using Verse;
// ReSharper disable InconsistentNaming
//[assembly: AllowPartiallyTrustedCallers]
//[assembly: SecurityTransparent]
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
[assembly: Debuggable(false, false)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: SecurityCritical(SecurityCriticalScope.Everything)]
namespace PerformanceFish.Tests;
[UsedImplicitly]
public class ReflectionCaching
{
internal static void InitializeLog(ITestOutputHelper output)
{
Log.Config.Message = output.WriteLine;
Log.Config.Warning = output.WriteLine;
Log.Config.Error = message => throw new(message);
Log.Config.Message = output.WriteLine; // message => output.WriteLine("\u001b[33m" + message);
Log.Config.Warning = output.WriteLine; // message => output.WriteLine("\u001b[33m" + message);
Log.Config.Error = static message => throw new(message);
}
// [Collection("Fish")]
// public class FishTableTest
// {
// public FishTableTest(ITestOutputHelper output) => InitializeLog(output);
//
// [Fact]
// public void Test()
// {
// const int COUNT = 538762;
//
// var randomBuffer = new byte[4 * COUNT];
// new Random(42).NextBytes(randomBuffer);
// var randomNumbers = new int[COUNT];
// Unsafe.CopyBlock(ref randomBuffer[0], ref Unsafe.As<int, byte>(ref randomNumbers[0]), 4 * COUNT);
//
// var fishTable = new FishTable<int, int>();
// foreach (var i in randomNumbers)
// {
// fishTable.GetOrAddReference(i) = -i;
// }
//
// foreach (var i in randomNumbers)
// {
// Assert.Equal(-i, fishTable.GetOrAddReference(i));
// }
// }
// }
#if false
[Collection("Fish")]
public class FishTableTest2
{
public FishTableTest2(ITestOutputHelper output) => InitializeLog(output);
[Fact]
public void Test()
{
var allTypes = GenTypes.AllTypes.ToArray();
var allTypeHandles = Array.ConvertAll(allTypes, static type => type.TypeHandle);
var hashCodes = Array.ConvertAll(allTypes, static type => type.TypeHandle.GetHashCode());
var hashCodeCounts = new Dictionary<int, uint>();
foreach (var code in hashCodes)
hashCodeCounts.GetOrAddReference(code)++;
var count = allTypes.Length;
Log.Message($"allTypes: {count}, hashCodes: {hashCodeCounts.Count}, max: {
hashCodeCounts.Max(static pair => pair.Value)}{Environment.NewLine}");
var random = new Random(42);
var randomNumbers = new int[count];
for (var i = 0; i < count; i++)
{
var randomNumber = random.Next();
while (randomNumber == 0)
randomNumber = random.Next();
randomNumbers[i] = randomNumber;
}
var success = true;
var fishTable = new FishTable<IntPtr, int>();
var addedEntries = 0;
fishTable.EntryAdded += _ => addedEntries++;
for (var i = 0; i < count; i++)
{
Exception caughtException = null;
try
{
try
{
fishTable.GetOrAddReference(allTypes[i].TypeHandle.Value) = randomNumbers[i];
try
{
VerifyCount(fishTable, i + 1);
}
catch (Exception e)
{
caughtException = e;
VerifyNumbers(randomNumbers, allTypeHandles, fishTable, i);
throw;
}
VerifyNumbers(randomNumbers, allTypeHandles, fishTable, i);
}
catch (Exception e)
{
throw new($"Inserted number: {randomNumbers[i]}, Retrieved number: {
fishTable.GetOrAddReference(allTypes[i].TypeHandle.Value)} {e}\n\n{caughtException}");
}
}
catch (Exception ex)
{
Log.Warning($"{ex}");
Log.Message(Environment.NewLine);
success = false;
// throw;
}
}
var failureCount = 0;
for (var i = 0; i < count; i++)
{
try
{
Assert.Equal(randomNumbers[i], fishTable.GetOrAddReference(allTypes[i].TypeHandle.Value));
}
catch (Exception ex)
{
failureCount++;
Log.Warning($"{ex}");
Log.Message(Environment.NewLine);
success = false;
}
}
Log.Message($"FailureCount: {failureCount}");
for (var i = 0; i < count; i++)
{
Assert.True(hashCodes[i].Equals<int>(allTypes[i].TypeHandle.GetHashCode()));
}
for (var i = 0; i < count; i++)
{
Assert.True(allTypeHandles[i].Equals<RuntimeTypeHandle>(allTypes[i].TypeHandle));
}
Assert.True(success);
VerifyCount(fishTable, allTypeHandles.Length);
Assert.Equal(count, addedEntries);
}
private static void VerifyNumbers(int[] expectedNumbers, RuntimeTypeHandle[] types,
FishTable<IntPtr, int> fishTable, int count)
{
for (var i = 0; i < count; i++)
{
var number = expectedNumbers[i];
var type = types[i];
try
{
Assert.True(number.Equals<int>(fishTable[type.Value]));
}
catch (Exception e)
{
throw new($"number: {number}, hashCode: {type.GetHashCode()}, i: {i}, size: {
fishTable.Count} {e}");
}
}
}
private static void VerifyCount(FishTable<IntPtr, int> fishTable, int expectedCount)
{
var realCount = 0;
var returnedCount = fishTable.Count;
foreach (var _ in fishTable)
{
realCount++;
}
if (realCount != returnedCount)
{
throw new($"Invalid count! real: {realCount}, returned: {returnedCount}, expected: {
expectedCount}");
}
}
}
#endif
[Collection("Fish")]
public class GetValue
{
public GetValue(ITestOutputHelper output) => InitializeLog(output);
private const int COUNT = 500;
private const int COUNT = 50000;
public static IEnumerable<object[]> Data
=> new object[][]
=> new[]
{
new[] { nameof(_enum) },
new[] { nameof(_nullableWithoutValue) },
new[] { nameof(_nullableWithValue) },
new[] { nameof(_primitive) },
new[] { nameof(_reference) },
new[] { nameof(CONST) }
new object[] { nameof(_enum) },
new object[] { nameof(_nullableWithoutValue) },
new object[] { nameof(_nullableWithValue) },
new object[] { nameof(_primitive) },
new object[] { nameof(_reference) },
new object[] { nameof(CONST) }
};
[Theory]
@@ -48,9 +256,9 @@ public class ReflectionCaching
object result = null;
for (var i = 0; i < COUNT; i++)
System.ReflectionCaching.FieldInfoPatches.GetValue_Patch.Prefix(field, null, ref result);
Assert.Equal(result, field.GetValue(null));
System.ReflectionCaching.Test.GetValue(field, null, ref result);
Assert.Equal(field.GetValue(null), result);
}
private static TypeCode _enum = TypeCode.Int32;
@@ -62,6 +270,7 @@ public class ReflectionCaching
private static Type _type = typeof(GetValue);
}
[Collection("Fish")]
public class SetValue
{
public SetValue(ITestOutputHelper output) => InitializeLog(output);
@@ -69,7 +278,7 @@ public class ReflectionCaching
private const int COUNT = 500;
public static IEnumerable<object[]> Data
=> new object[][]
=> new[]
{
new object[] { nameof(_enum), TypeCode.Byte },
new object[] { nameof(_nullableWithoutValue), 1 },
@@ -86,11 +295,87 @@ public class ReflectionCaching
Assert.NotNull(field);
for (var i = 0; i < COUNT; i++)
System.ReflectionCaching.FieldInfoPatches.SetValue_Patch.Prefix(field, null, input);
System.ReflectionCaching.Test.SetValue(field, null, input);
Assert.Equal(input, field.GetValue(null));
}
[Fact]
public void Test2()
{
SimpleThingDefOf.Initialize();
var def = SimpleThingDefOf.Instance;
Assert.NotNull(def.Description);
a(def, "descriptionDetailedCached", null);
Assert.Null(def.Description);
Assert.NotEqual(default, def.Label);
a(def, "cachedLabelCap", null);
Assert.Equal(default, def.Label);
}
[Fact]
public void Test3()
{
for (var i = 0; i < COUNT; i++)
{
SimpleThingDefOf.Initialize();
var def = SimpleThingDefOf.Instance;
Assert.NotNull(def.Description);
a_Optimized(def, "descriptionDetailedCached", null);
Assert.Null(def.Description);
Assert.NotEqual(default, def.Label);
a_Optimized(def, "cachedLabelCap", null);
Assert.Equal(default, def.Label);
}
}
[Fact]
public void Test4()
{
for (var i = 0; i < 2; i++)
{
_primitive = 0;
var field = GetType().GetField(nameof(_primitive), AccessTools.all);
IntPtr value = (nint)(-476);
var failed = false;
try
{
if (i == 0)
field.SetValue(null, value);
else
System.ReflectionCaching.Test.SetValue(field, null, value);
}
catch (Exception e)
{
failed = true;
Log.Message(e + "\n");
}
Assert.True(failed);
// Assert.Equal(-476, field.GetValue(null));
}
}
private static void a(object A_0, string A_1, object A_2)
=> (A_0?.GetType().GetField(A_1, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
?.SetValue(A_0, A_2);
private static void a_Optimized(object A_0, string A_1, object A_2)
{
if (A_0?.GetType().GetField(A_1, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
is { } field)
{
System.ReflectionCaching.Test.SetValue(field, A_0, A_2);
}
}
private static TypeCode _enum = TypeCode.Int32;
private static int? _nullableWithoutValue = null;
private static int? _nullableWithValue = 1;
@@ -98,4 +383,138 @@ public class ReflectionCaching
private static object _reference = new();
private static Type _type = typeof(SetValue);
}
public class SimpleThingDef
{
private string descriptionDetailedCached;
private TaggedString cachedLabelCap;
public string Description => descriptionDetailedCached;
public TaggedString Label
{
get => cachedLabelCap;
set => cachedLabelCap = value;
}
public SimpleThingDef(string description, string label)
{
descriptionDetailedCached = description;
cachedLabelCap = label;
}
}
public static class SimpleThingDefOf
{
public static SimpleThingDef Instance;
public static void Initialize() => Instance = new("InitialDescription", "InitialLabel");
}
[Collection("Fish")]
public class Invoke
{
public Invoke(ITestOutputHelper output) => InitializeLog(output);
private const int COUNT = 500;
public static IEnumerable<object[]> Data
=> new[]
{
new object[]
{
AccessTools.Method(typeof(Scribe_Values), nameof(Scribe_Values.Look),
generics: new[] { typeof(string) }),
new object[] { "", "", "", false }
}
};
// [Theory]
// [MemberData(nameof(Data))]
// public void Test(MethodBase method, object[] input)
// {
// Assert.NotNull(method);
//
// object result = null;
//
// for (var i = 0; i < COUNT; i++)
// System.ReflectionCaching.Test.Invoke(method, null, input, ref result);
// }
[Fact]
public void TestString()
{
var method = AccessTools.Method(GetType(), nameof(Look), generics: new[] { typeof(string) });
var value = "Input";
var label = "Label";
var defaultValue = "DefaultValue";
var forceSave = true;
var parameters = new object[] { value, label, defaultValue, forceSave };
var input = MakeString(value, label, defaultValue, forceSave);
object output = null;
System.ReflectionCaching.Test.Invoke(method, null, parameters, ref output);
Assert.Equal(input, output);
Assert.Equal("Test", parameters[0]);
}
[Fact]
public void TestIntOptimized()
{
var method = AccessTools.Method(GetType(), nameof(Look), generics: new[] { typeof(int) });
var value = 5;
var label = "Label";
var defaultValue = 0;
var forceSave = false;
var parameters = new object[] { value, label, defaultValue, forceSave };
var input = MakeString(value, label, defaultValue, forceSave);
object output = null;
System.ReflectionCaching.Test.Invoke(method, null, parameters, ref output);
Assert.Equal(input, output);
Assert.Equal(17, parameters[0]);
}
[Fact]
public void TestIntExpected()
{
var value = 5;
var label = "Label";
var defaultValue = 0;
var forceSave = false;
var input = MakeString(value, label, defaultValue, forceSave);
var output = Look(ref value, label, defaultValue, forceSave);
Assert.Equal(input, output);
Assert.Equal(17, value);
}
private static string Look<T>(ref T value, string label, T defaultValue = default, params object[] ctorArgs)
{
var input = MakeString(value, label, defaultValue, ctorArgs);
if (typeof(string).IsAssignableFrom(typeof(T)))
value = (T)(object)"Test";
else if (typeof(int).IsAssignableFrom(typeof(T)))
value = (T)(object)17;
return input;
}
private static string MakeString<T>(T value, string label, T defaultValue, params object[] ctorArgs)
=> $"value: {value}, label: {label}, defaultValue: {defaultValue}, ctorArgs: {
ctorArgs.ToStringSafeEnumerable()}";
}
}