Performance Fish
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ModMetaData>
|
||||
<name>Performance Fish</name>
|
||||
<packageId>bs.performance</packageId>
|
||||
<supportedVersions>
|
||||
<li>1.3</li>
|
||||
<li>1.4</li>
|
||||
</supportedVersions>
|
||||
<author>bradson</author>
|
||||
<url>https://github.com/bbradson/performance-fish</url>
|
||||
<loadBefore>
|
||||
<li>Dubwise.DubsPerformanceAnalyzer</li>
|
||||
<li>Dubwise.DubsPerformanceAnalyzerButchered</li>
|
||||
<li>Ludeon.RimWorld.Royalty</li>
|
||||
<li>Ludeon.RimWorld.Ideology</li>
|
||||
<li>Ludeon.RimWorld.Biotech</li>
|
||||
<li>UnlimitedHugs.HugsLib</li>
|
||||
</loadBefore>
|
||||
<loadAfter>
|
||||
<li>brrainz.harmony</li>
|
||||
<li>me.samboycoding.betterloading</li>
|
||||
<li>zetrith.prepatcher</li>
|
||||
<li>Ludeon.RimWorld</li>
|
||||
<li>bs.betterlog</li>
|
||||
</loadAfter>
|
||||
<description>download more tps</description>
|
||||
</ModMetaData>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
@@ -0,0 +1,189 @@
|
||||
<Analyzer>
|
||||
|
||||
<PerformanceFish>
|
||||
<!-- Settings, Tick, Update, GUI or Modder. Defaults to Modder -->
|
||||
<!--Category>Modder</Category-->
|
||||
|
||||
<!-- On hover tooltip for the sidebar entry -->
|
||||
<Tooltip>Methods patched and optimized by Performance Fish. Use this in conjunction with its mod settings to measure performance improvements or find eventual issues if a patch acts up</Tooltip>
|
||||
|
||||
<!-- All methods in types -->
|
||||
<!--Types>
|
||||
<li></li>
|
||||
</Types-->
|
||||
|
||||
<!-- Specific methods -->
|
||||
<Methods>
|
||||
<!-- DesignationManager -->
|
||||
<li>Verse.DesignationManager:SpawnedDesignationsOfDef</li>
|
||||
<li>Verse.DesignationManager:AnySpawnedDesignationOfDef</li>
|
||||
<li>Verse.DesignationManager:AllDesignationsOn</li>
|
||||
<li>Verse.DesignationManager:DesignationOn</li>
|
||||
<li>Verse.DesignationManager:HasMapDesignationOn</li>
|
||||
|
||||
<!-- Misc -->
|
||||
<!--li>Verse.GenList:ListFullCopy</li--><!-- generic -->
|
||||
<li>Verse.ThingFilter:CopyAllowancesFrom</li>
|
||||
<li>Verse.DrawBatch:Flush</li>
|
||||
|
||||
<!-- HediffComp_PsychicHarmonizer -->
|
||||
<li>RimWorld.HediffComp_PsychicHarmonizer:CompPostTick</li>
|
||||
|
||||
<!-- HediffSet -->
|
||||
<li>Verse.HediffSet:HasHediff</li>
|
||||
|
||||
<!-- Lister -->
|
||||
<li>Verse.Map:MapPreTick</li>
|
||||
<li>Verse.ListerBuildings:ColonistsHaveBuilding</li>
|
||||
<li>Verse.ListerBuildings:ColonistsHaveResearchBench</li>
|
||||
<li>Verse.ListerBuildings:ColonistsHaveBuildingWithPowerOn</li>
|
||||
<li>Verse.ListerBuildings:AllBuildingsColonistOfDef</li>
|
||||
<li>Verse.ListerBuildings:AllBuildingsNonColonistOfDef</li>
|
||||
|
||||
<!-- StorageSettings -->
|
||||
<li>RimWorld.StorageSettings:AllowedToAccept</li>
|
||||
|
||||
<!-- MassUtility -->
|
||||
<li>RimWorld.MassUtility:GearMass</li>
|
||||
<li>RimWorld.MassUtility:InventoryMass</li>
|
||||
|
||||
<!-- RecipeDef -->
|
||||
<li>Verse.RecipeDef:get_AllRecipeUsers</li>
|
||||
<li>Verse.UnfinishedThing:BillOnTableForMe</li>
|
||||
|
||||
<!-- Room -->
|
||||
<!-- Verse.Room:get_ContainedBeds and Verse.Room:get_Owners are almost exclusively used by Pawn_Ownership.get_OwnedRoom -->
|
||||
<li>RimWorld.Pawn_Ownership:get_OwnedRoom</li>
|
||||
<li>Verse.Room:get_Regions</li>
|
||||
<li>Verse.Room:get_ContainedAndAdjacentThings</li>
|
||||
|
||||
<!-- StoreUtility -->
|
||||
|
||||
<!-- ThingFilter -->
|
||||
<li>Verse.ThingFilter:get_AllowedThingDefs</li>
|
||||
<li>Verse.ThingFilter:get_AllStorableThingDefs</li>
|
||||
|
||||
<!-- ThingOwnerUtility -->
|
||||
<!--li>Verse.ThingOwnerUtility.GetAllThingsRecursively</li--><!-- generic -->
|
||||
<li>Verse.MapPawns:get_AllPawnsUnspawned</li>
|
||||
|
||||
<!-- ThoughtWorker_Precept_AltarSharing -->
|
||||
<li>RimWorld.ThoughtWorker_Precept_AltarSharing:ShouldHaveThought</li>
|
||||
|
||||
<!-- GenClosest -->
|
||||
|
||||
<!-- Utility -->
|
||||
|
||||
<!-- WorkGiver_DoBill -->
|
||||
<!--li>RimWorld.WorkGiver_DoBill:TryFindBestIngredientsHelper</li-->
|
||||
<!--li>RimWorld.WorkGiver_DoBill+<>c__DisplayClass21_0:<TryFindBestIngredientsHelper>b__4</li-->
|
||||
|
||||
<!-- WorkGiver_Haul -->
|
||||
<!--li>RimWorld.WorkGiver_Haul:PotentialWorkThingsGlobal</li-->
|
||||
<li>PerformanceFish.JobSystem.WorkGiver_HaulOptimization+PotentialWorkThingsGlobal_Patch:Sort</li>
|
||||
<li>RimWorld.WorkGiver_Haul:JobOnThing</li>
|
||||
|
||||
<!-- JobGiver_Haul -->
|
||||
<li>RimWorld.JobGiver_Haul:TryGiveJob</li>
|
||||
|
||||
<!-- IdeoUtility -->
|
||||
<li>RimWorld.IdeoUtility:GetStyleDominance</li>
|
||||
|
||||
<!-- ThingCompPatches -->
|
||||
<li>RimWorld.CompAssignableToPawn_Bed:IdeoligionForbids</li>
|
||||
|
||||
<!-- Reflection -->
|
||||
<li>System.RuntimeType:GetField</li>
|
||||
<li>System.Type:GetMethod</li>
|
||||
<li>System.Type:GetConstructor</li>
|
||||
<li>System.Type:GetProperty</li>
|
||||
<li>System.Reflection.MonoField:GetValue</li>
|
||||
<li>System.Reflection.FieldInfo:SetValue</li>
|
||||
<li>System.Reflection.MethodBase:Invoke</li>
|
||||
</Methods>
|
||||
|
||||
<!-- Specific methods of all subclasses -->
|
||||
<!--DerivedMethods>
|
||||
<li></li>
|
||||
</DerivedMethods-->
|
||||
|
||||
<!-- All methods of subclasses -->
|
||||
<!--DerivedTypes>
|
||||
<li></li>
|
||||
</DerivedTypes-->
|
||||
|
||||
<!-- All methods of types nested within specified types -->
|
||||
<!--NestedTypes>
|
||||
<li></li>
|
||||
</NestedTypes-->
|
||||
</PerformanceFish>
|
||||
|
||||
<ThingComps>
|
||||
<Tooltip>Update methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones</Tooltip>
|
||||
<Category>Update</Category>
|
||||
<DerivedMethods>
|
||||
<li>Verse.ThingComp:DrawGUIOverlay</li>
|
||||
<li>Verse.ThingComp:PostDraw</li>
|
||||
<li>Verse.ThingComp:PostDrawExtraSelectionOverlays</li>
|
||||
<li>Verse.ThingComp:PostPrintOnto</li>
|
||||
</DerivedMethods>
|
||||
</ThingComps>
|
||||
|
||||
<Hediffs>
|
||||
<Tooltip>Ticking health stuff ond other things implanted in pawn bodies</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>Verse.Hediff:Tick</li>
|
||||
<li>Verse.Hediff:PostTick</li>
|
||||
</DerivedMethods>
|
||||
</Hediffs>
|
||||
|
||||
<HediffComps>
|
||||
<Tooltip>Comps attached to Hediffs. Often responsible for the actual logic</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>Verse.HediffComp:CompPostTick</li>
|
||||
</DerivedMethods>
|
||||
</HediffComps>
|
||||
|
||||
<WorldObjects>
|
||||
<Tooltip>Settlements, caravans etc</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>RimWorld.Planet.WorldObject:Tick</li>
|
||||
</DerivedMethods>
|
||||
</WorldObjects>
|
||||
|
||||
<WorldObjectComps>
|
||||
<Tooltip>Comps attached to settlements, caravans etc</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>RimWorld.Planet.WorldObjectComp:CompTick</li>
|
||||
</DerivedMethods>
|
||||
</WorldObjectComps>
|
||||
|
||||
<WorldComponentTick>
|
||||
<Tooltip>Tick methods of comps with one instance attached to the game world</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>RimWorld.Planet.WorldComponent:WorldComponentTick</li>
|
||||
</DerivedMethods>
|
||||
</WorldComponentTick>
|
||||
|
||||
<WorldComponentUpdate>
|
||||
<Tooltip>Update methods of comps with one instance attached to the game world</Tooltip>
|
||||
<Category>Update</Category>
|
||||
<DerivedMethods>
|
||||
<li>RimWorld.Planet.WorldComponent:WorldComponentUpdate</li>
|
||||
</DerivedMethods>
|
||||
</WorldComponentUpdate>
|
||||
|
||||
<MentalStates>
|
||||
<Tooltip>Tick methods of mental states like berserk, tantrum etc</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>Verse.AI.MentalState:MentalStateTick</li>
|
||||
</DerivedMethods>
|
||||
</MentalStates>
|
||||
|
||||
</Analyzer>
|
||||
@@ -1,3 +1,5 @@
|
||||
Copyright (c) 2021 bradson
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
@@ -35,7 +37,7 @@ Mozilla Public License Version 2.0
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
@@ -357,7 +359,7 @@ Exhibit A - Source Code Form License Notice
|
||||
|
||||
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 http://mozilla.org/MPL/2.0/.
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
@@ -370,4 +372,4 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Performance Fish
|
||||

|
||||
|
||||
Performance Mod for Rimworld.
|
||||
Performance Fish attempts to improve overall framerates and tick times by patching various methods for improved efficiency, while keeping functionality identical. Designed to be used alongside other performance mods, like RocketMan, and intended to be compatible with very large modlists. Most patches become more impactful as the game progresses further into the lategame.
|
||||
|
||||
A settings menu includes short descriptions for every patch, and each of them can be freely toggled in there. They get entirely unpatched in a disabled state, immediately. Nothing is stored in savefiles of specific game-sessions, ensuring that no errors get thrown when removing this mod.
|
||||
Special patches exist to add new entries and features to Dub's Performance Analyzer, including a right-click function allowing to profile overrides of functions.
|
||||
|
||||
Multiplayer and RimThreaded currently aren't supported. All other mods, including Combat Extended, Vanilla Expanded, RocketMan and Performance Optimizer, are compatible.
|
||||
|
||||
Licensed under [MPL-2.0](https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2))
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<EnableNETAnalyzers>True</EnableNETAnalyzers>
|
||||
<AnalysisLevel>preview-all</AnalysisLevel>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<Configurations>v1.3;v1.4</Configurations>
|
||||
</PropertyGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(Configuration)' == 'v1.3'">
|
||||
<ItemGroup>
|
||||
<!--PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" /-->
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>E:\RimWorld\1.3\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib">
|
||||
<HintPath>E:\RimWorld\1.3\RimWorldWin64_Data\Managed\mscorlib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>E:\RimWorld\1.3\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule">
|
||||
<HintPath>E:\RimWorld\1.3\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.3\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PerformanceFish">
|
||||
<HintPath>..\..\1.3\Assemblies\PerformanceFish.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition="'$(Configuration)' == 'v1.4'">
|
||||
<ItemGroup>
|
||||
<!--PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*-*" /-->
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>E:\RimWorld\1.4\RimWorldWin64_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib">
|
||||
<HintPath>E:\RimWorld\1.4\RimWorldWin64_Data\Managed\mscorlib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>E:\RimWorld\1.4\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule">
|
||||
<HintPath>E:\RimWorld\1.4\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.4\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PerformanceFish">
|
||||
<HintPath>..\..\1.4\Assemblies\PerformanceFish.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.*" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
|
||||
<PackageReference Include="xunit" Version="2.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32728.150
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceFish.Tests", "PerformanceFish.Tests.csproj", "{D3852300-E6CA-4561-849F-B9F827703B3A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
v1.3|Any CPU = v1.3|Any CPU
|
||||
v1.4|Any CPU = v1.4|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D3852300-E6CA-4561-849F-B9F827703B3A}.v1.3|Any CPU.ActiveCfg = v1.3|Any CPU
|
||||
{D3852300-E6CA-4561-849F-B9F827703B3A}.v1.3|Any CPU.Build.0 = v1.3|Any CPU
|
||||
{D3852300-E6CA-4561-849F-B9F827703B3A}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU
|
||||
{D3852300-E6CA-4561-849F-B9F827703B3A}.v1.4|Any CPU.Build.0 = v1.4|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9E98D95E-68FE-465E-A330-11540608ADAD}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,101 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using Xunit;
|
||||
global using Xunit.Abstractions;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace PerformanceFish.Tests;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public class GetValue
|
||||
{
|
||||
public GetValue(ITestOutputHelper output) => InitializeLog(output);
|
||||
|
||||
private const int COUNT = 500;
|
||||
|
||||
public static IEnumerable<object[]> Data
|
||||
=> new object[][]
|
||||
{
|
||||
new[] { nameof(_enum) },
|
||||
new[] { nameof(_nullableWithoutValue) },
|
||||
new[] { nameof(_nullableWithValue) },
|
||||
new[] { nameof(_primitive) },
|
||||
new[] { nameof(_reference) },
|
||||
new[] { nameof(CONST) }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Data))]
|
||||
public void Test(string name)
|
||||
{
|
||||
if (name == nameof(CONST))
|
||||
{
|
||||
Log.Message("sPeCiFiEd MeThOd Is NoT sUpPoRtEd!\nworks fine on mono however");
|
||||
return;
|
||||
}
|
||||
|
||||
var field = _type.GetField(name, AccessTools.all);
|
||||
Assert.NotNull(field);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private static TypeCode _enum = TypeCode.Int32;
|
||||
private static int? _nullableWithoutValue = null;
|
||||
private static int? _nullableWithValue = 1;
|
||||
private static int _primitive = 0;
|
||||
private static object _reference = new();
|
||||
private const string CONST = "a";
|
||||
private static Type _type = typeof(GetValue);
|
||||
}
|
||||
|
||||
public class SetValue
|
||||
{
|
||||
public SetValue(ITestOutputHelper output) => InitializeLog(output);
|
||||
|
||||
private const int COUNT = 500;
|
||||
|
||||
public static IEnumerable<object[]> Data
|
||||
=> new object[][]
|
||||
{
|
||||
new object[] { nameof(_enum), TypeCode.Byte },
|
||||
new object[] { nameof(_nullableWithoutValue), 1 },
|
||||
new object[] { nameof(_nullableWithValue), null },
|
||||
new object[] { nameof(_primitive), 1 },
|
||||
new object[] { nameof(_reference), new() }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Data))]
|
||||
public void Test(string name, object input)
|
||||
{
|
||||
var field = _type.GetField(name, AccessTools.all);
|
||||
Assert.NotNull(field);
|
||||
|
||||
for (var i = 0; i < COUNT; i++)
|
||||
System.ReflectionCaching.FieldInfoPatches.SetValue_Patch.Prefix(field, null, input);
|
||||
|
||||
Assert.Equal(input, field.GetValue(null));
|
||||
}
|
||||
|
||||
private static TypeCode _enum = TypeCode.Int32;
|
||||
private static int? _nullableWithoutValue = null;
|
||||
private static int? _nullableWithValue = 1;
|
||||
private static int _primitive = 0;
|
||||
private static object _reference = new();
|
||||
private static Type _type = typeof(SetValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
[*.cs]
|
||||
|
||||
file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
# CA2211: Non-constant fields should not be visible
|
||||
dotnet_diagnostic.CA2211.severity = none
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_expression_bodied_methods = true:suggestion
|
||||
csharp_style_expression_bodied_constructors = true:suggestion
|
||||
csharp_style_expression_bodied_operators = true:suggestion
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
csharp_style_expression_bodied_indexers = true:suggestion
|
||||
csharp_style_expression_bodied_accessors = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||
csharp_style_expression_bodied_local_functions = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:silent
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
[*.cs]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.constant_should_be_all_upper.severity = suggestion
|
||||
dotnet_naming_rule.constant_should_be_all_upper.symbols = constant
|
||||
dotnet_naming_rule.constant_should_be_all_upper.style = all_upper
|
||||
|
||||
dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.severity = suggestion
|
||||
dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.symbols = private_or_protected_field
|
||||
dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.style = camel_case_with__
|
||||
|
||||
dotnet_naming_rule.public_or_internal_field_should_be_camel_case.severity = silent
|
||||
dotnet_naming_rule.public_or_internal_field_should_be_camel_case.symbols = public_or_internal_field
|
||||
dotnet_naming_rule.public_or_internal_field_should_be_camel_case.style = camel_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.constant.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constant.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_protected_field.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.private_or_protected_field.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.public_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.public_or_internal_field.applicable_accessibilities = public, internal
|
||||
dotnet_naming_symbols.public_or_internal_field.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.all_upper.required_prefix =
|
||||
dotnet_naming_style.all_upper.required_suffix =
|
||||
dotnet_naming_style.all_upper.word_separator = _
|
||||
dotnet_naming_style.all_upper.capitalization = all_upper
|
||||
|
||||
dotnet_naming_style.camel_case_with__.required_prefix = _
|
||||
dotnet_naming_style.camel_case_with__.required_suffix =
|
||||
dotnet_naming_style.camel_case_with__.word_separator =
|
||||
dotnet_naming_style.camel_case_with__.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.camel_case.required_prefix =
|
||||
dotnet_naming_style.camel_case.required_suffix =
|
||||
dotnet_naming_style.camel_case.word_separator =
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
dotnet_diagnostic.CA1805.severity = none
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:suggestion
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
[*.{cs,vb}]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_diagnostic.CA1838.severity = suggestion
|
||||
dotnet_diagnostic.CA1848.severity = suggestion
|
||||
dotnet_diagnostic.CA2215.severity = suggestion
|
||||
dotnet_style_readonly_field = true:silent
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||
dotnet_code_quality_unused_parameters = non_public:suggestion
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Xml;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public static class AnalyzerFixes
|
||||
{
|
||||
public static void Patch()
|
||||
{
|
||||
var modBase = AccessTools.Constructor(Reflection.Type("PerformanceAnalyzer", "Analyzer.Modbase"), new[] { typeof(ModContentPack) });
|
||||
if (modBase is null)
|
||||
return /*false*/;
|
||||
|
||||
var success = PerformanceFishMod.Harmony.Patch(modBase, postfix: new(methodof(XmlParser.CollectXmlData))) != null;
|
||||
|
||||
var methodTransplanting = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.MethodTransplanting", "UpdateMethod",
|
||||
new[] { typeof(Type), typeof(MethodInfo) });
|
||||
|
||||
if (methodTransplanting is null
|
||||
|| PerformanceFishMod.Harmony.Patch(methodTransplanting, transpiler: new(methodof(UpdateMethod_Transpiler))) is null)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
var panelLogs = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Panel_Logs", "RightClickDropDown");
|
||||
|
||||
if (panelLogs is null
|
||||
|| PerformanceFishMod.Harmony.Patch(panelLogs, postfix: new(methodof(PanelLogs_Postfix))) is null)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
return /*success*/;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// yes this is literally a prefix
|
||||
/// </summary>
|
||||
public static CodeInstructions UpdateMethod_Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
yield return FishTranspiler.ArgumentAddress(method, "meth");
|
||||
yield return FishTranspiler.Call(UpdateMethod_Prefix);
|
||||
foreach (var code in codes)
|
||||
yield return code;
|
||||
}
|
||||
|
||||
public static void UpdateMethod_Prefix(ref MethodInfo meth)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(meth) is { } moveNext)
|
||||
meth = moveNext;
|
||||
}
|
||||
|
||||
public static IEnumerable<FloatMenuOption> PanelLogs_Postfix(IEnumerable<FloatMenuOption> values, object log)
|
||||
{
|
||||
var method = ProfileLog_meth(log) as MethodInfo;
|
||||
var returned = false;
|
||||
foreach (var value in values)
|
||||
{
|
||||
yield return value;
|
||||
if (IsValidMethodForOverriding(method) && !returned && value.Label == "Profile the internal methods of")
|
||||
{
|
||||
returned = true;
|
||||
yield return new("Profile overriding methods of",
|
||||
() => PatchOverridesForMethod(method, (Category)GUIController_CurrentCategory()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidMethodForOverriding([NotNullWhen(true)] MethodInfo? info)
|
||||
=> info is not null
|
||||
&& (info.IsVirtual || info.IsAbstract || info.GetBaseDefinition().DeclaringType != info.DeclaringType);
|
||||
|
||||
public enum Category
|
||||
{
|
||||
Settings,
|
||||
Tick,
|
||||
Update,
|
||||
GUI,
|
||||
Modder
|
||||
}
|
||||
|
||||
public static void PatchOverridesForMethod(MethodInfo method, Category category)
|
||||
{
|
||||
if (OverridingMethodUtility.PatchedOverrides.Contains(method))
|
||||
{
|
||||
var signature = Utility_GetSignature(method, true);
|
||||
Messages.Message("Have already patched overrides for the method " + signature, MessageTypeDefOf.CautionInput, false);
|
||||
Utility_Warn("Trying to repeat patching already profiled overrides of method - " + signature);
|
||||
}
|
||||
else
|
||||
{
|
||||
PatchOverridesForMethodFull(method, category);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PatchOverridesForMethodFull(MethodInfo method, Category category)
|
||||
{
|
||||
try
|
||||
{
|
||||
OverridingMethodUtility.Generate(method, category);
|
||||
/*Task.Factory.StartNew(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
Modbase_Harmony().Patch(method, null, null, OverridingMethodUtility.OverrideProfiler);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
Utility_ReportException(e2, "Failed to patch overriding methods for " + Utility_GetSignature(method, false));
|
||||
}
|
||||
});*/
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utility_ReportException(e, "Failed to set up state to patch overriding methods");
|
||||
OverridingMethodUtility.PatchedOverrides.Remove(method);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OverridingMethodUtility
|
||||
{
|
||||
public static HashSet<MethodInfo> PatchedOverrides = new();
|
||||
|
||||
public static void ClearCaches() => PatchedOverrides.Clear();
|
||||
|
||||
public static void Generate(MethodInfo method, Category category)
|
||||
{
|
||||
PatchedOverrides.Add(method);
|
||||
|
||||
var name = $"{method.DeclaringType}:{method.Name}-overrides";
|
||||
|
||||
var methods = new HashSet<MethodInfo>();
|
||||
var subClasses = method.DeclaringType.AllSubclasses();
|
||||
|
||||
foreach (var subClass in subClasses)
|
||||
{
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared), m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
if (overridingMethod is null || overridingMethod.IsAbstract)
|
||||
continue;
|
||||
|
||||
methods.Add(overridingMethod);
|
||||
|
||||
//Log.Message($"Added method {Utility_GetSignature(overridingMethod, true)} for profiling");
|
||||
}
|
||||
|
||||
var type = DynamicTypeBuilder_CreateType(name, methods);
|
||||
var entry = Entry_Create(name, (int)category, type, false, true);
|
||||
GUIController_AddSpecificEntry((int)category, entry, type);
|
||||
GUIController_SwapToEntry(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static AccessTools.FieldRef<object, MethodBase> ProfileLog_meth
|
||||
=> _profileLog_meth
|
||||
??= AccessTools.FieldRefAccess<object, MethodBase>(Reflection.FieldInfo("PerformanceAnalyzer", "Analyzer.Profiling.ProfileLog", "meth"));
|
||||
private static AccessTools.FieldRef<object, MethodBase>? _profileLog_meth;
|
||||
|
||||
public static Func<Harmony> Modbase_Harmony
|
||||
=> _modbase_Harmony
|
||||
??= (Func<Harmony>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Modbase", "get_Harmony")
|
||||
.CreateDelegate(typeof(Func<Harmony>));
|
||||
private static Func<Harmony>? _modbase_Harmony;
|
||||
|
||||
public static Action<string, int> GUIController_AddEntry
|
||||
=> _guiController_AddEntry
|
||||
??= (Action<string, int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "AddEntry")
|
||||
.CreateDelegate(typeof(Action<string, int>));
|
||||
private static Action<string, int>? _guiController_AddEntry;
|
||||
|
||||
public static Action<string> GUIController_SwapToEntry
|
||||
=> _guiController_SwapToEntry
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "SwapToEntry")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _guiController_SwapToEntry;
|
||||
|
||||
public static Func<MethodBase, bool, string> Utility_GetSignature
|
||||
=> _utility_GetSignature
|
||||
??= (Func<MethodBase, bool, string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetSignature")
|
||||
.CreateDelegate(typeof(Func<MethodBase, bool, string>));
|
||||
private static Func<MethodBase, bool, string>? _utility_GetSignature;
|
||||
|
||||
public static Func<int> GUIController_CurrentCategory
|
||||
=> _guiController_CurrentCategory
|
||||
??= (Func<int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "get_CurrentCategory")
|
||||
.CreateDelegate(typeof(Func<int>));
|
||||
private static Func<int>? _guiController_CurrentCategory;
|
||||
|
||||
public static Action<string> Utility_Warn
|
||||
=> _utility_Warn
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Warn")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _utility_Warn;
|
||||
|
||||
public static Action<Exception, string> Utility_ReportException
|
||||
=> _utility_ReportException
|
||||
??= (Action<Exception, string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "ReportException")
|
||||
.CreateDelegate(typeof(Action<Exception, string>));
|
||||
private static Action<Exception, string>? _utility_ReportException;
|
||||
|
||||
public static Action<string> Utility_Error
|
||||
=> _utility_Error
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Error")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _utility_Error;
|
||||
|
||||
public static CodeInstructions Parse_Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
var parseMethod = FishTranspiler.Call("PerformanceAnalyzer", "Analyzer.Profiling.XmlParser", "ParseMethod");
|
||||
var errorMethod = FishTranspiler.Call("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error");
|
||||
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (codes[i] == parseMethod)
|
||||
{
|
||||
yield return new(codes[i - 2]); //Ldloc xmlNode
|
||||
yield return FishTranspiler.Call(ParseMethod_Fixed);
|
||||
yield return FishTranspiler.Call<Action<HashSet<MethodInfo>, IEnumerable<MethodInfo>>>(GenCollection.AddRange);
|
||||
i += 2; //removing OpCodes.Pop
|
||||
}
|
||||
else if (codes[i] == errorMethod)
|
||||
{
|
||||
yield return new(codes[i - 4]); //Ldloc xmlNode
|
||||
yield return FishTranspiler.Call(ErrorFix);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodInfo> ParseMethod_Fixed(string info, XmlNode node)
|
||||
=> node.NodeType == XmlNodeType.Comment
|
||||
? Enumerable.Empty<MethodInfo>()
|
||||
: ParseMethod(info);
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseMethod(string info)
|
||||
{
|
||||
var array = info.Split(':');
|
||||
if (array.Length != 2)
|
||||
throw new ArgumentException($"Method must be specified as 'Namespace.Type1.Type2:MethodName. Got {info} instead.", nameof(info));
|
||||
|
||||
var methods = Reflection.MethodsOfName(AccessTools.TypeByName(array[0]), array[1]);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
yield return method.IsGenericMethodDefinition
|
||||
? method.MakeGenericMethod(method.GetGenericArguments().Select(t => t.GetGenericParameterConstraints() is var constraints2 && constraints2.Length != 0 ? constraints2[0] : typeof(object)).ToArray())
|
||||
: method;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ErrorFix(string message, XmlNode node)
|
||||
{
|
||||
if (node.NodeType == XmlNodeType.Comment)
|
||||
return;
|
||||
|
||||
ThreadSafeLogger_Error(message);
|
||||
}
|
||||
|
||||
public static Action<string> ThreadSafeLogger_Error
|
||||
=> _threadSafeLogger_Error
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _threadSafeLogger_Error;
|
||||
|
||||
public static Func<string, HashSet<MethodInfo>, Type> DynamicTypeBuilder_CreateType
|
||||
=> _dynamicTypeBuilder_CreateType
|
||||
??= (Func<string, HashSet<MethodInfo>, Type>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.DynamicTypeBuilder", "CreateType")
|
||||
.CreateDelegate(typeof(Func<string, HashSet<MethodInfo>, Type>));
|
||||
private static Func<string, HashSet<MethodInfo>, Type>? _dynamicTypeBuilder_CreateType;
|
||||
|
||||
public static Func<Type, bool, IEnumerable<MethodInfo>> Utility_GetTypeMethods
|
||||
=> _utility_GetTypeMethods
|
||||
??= (Func<Type, bool, IEnumerable<MethodInfo>>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetTypeMethods")
|
||||
.CreateDelegate(typeof(Func<Type, bool, IEnumerable<MethodInfo>>));
|
||||
private static Func<Type, bool, IEnumerable<MethodInfo>>? _utility_GetTypeMethods;
|
||||
|
||||
public static AccessTools.FieldRef<object, string> Entry_tip
|
||||
=> _entry_tip
|
||||
??= AccessTools.FieldRefAccess<string>(Type.GetType("Analyzer.Profiling.Entry, PerformanceAnalyzer"), "tip");
|
||||
private static AccessTools.FieldRef<object, string>? _entry_tip;
|
||||
|
||||
public static Action<int, object, Type> GUIController_AddSpecificEntry => _guiController_AddSpecificEntry ??= MakeGUIController_AddEntryMethod();
|
||||
private static Action<int, object, Type>? _guiController_AddSpecificEntry;
|
||||
private static Action<int, object, Type> MakeGUIController_AddEntryMethod()
|
||||
{
|
||||
var analyzer_Profiling_EntryType = Reflection.Type("PerformanceAnalyzer", "Analyzer.Profiling.Entry");
|
||||
if (analyzer_Profiling_EntryType == null)
|
||||
Log.Error("analyzer_Profiling_EntryType is null");
|
||||
|
||||
var gUIController_Tab = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "Tab");
|
||||
if (gUIController_Tab == null)
|
||||
Log.Error("gUIController_Tab is null");
|
||||
|
||||
var tab_entries = Reflection.FieldInfo("PerformanceAnalyzer", "Analyzer.Profiling.Tab", "entries");
|
||||
if (tab_entries == null)
|
||||
Log.Error("tab_entries is null");
|
||||
|
||||
/*var memberInfo_Name = AccessTools.PropertyGetter(typeof(MemberInfo), nameof(MemberInfo.Name));
|
||||
if (memberInfo_Name == null)
|
||||
Log.Error("memberInfo_Name is null");
|
||||
|
||||
var entry_Create = AccessTools.Method(analyzer_Profiling_EntryType, "Create");
|
||||
if (entry_Create == null)
|
||||
Log.Error("entry_Create is null");*/
|
||||
|
||||
var dictionary_Add = AccessTools.Method(typeof(Dictionary<,>).MakeGenericType(new[] { analyzer_Profiling_EntryType, typeof(Type) }),
|
||||
"Add", new[] { analyzer_Profiling_EntryType, typeof(Type) });
|
||||
if (dictionary_Add == null)
|
||||
Log.Error("dictionary_Add is null");
|
||||
|
||||
var dynamicMethod = new DynamicMethod("GUIController_AddEntryMethod", typeof(void), new[] { typeof(int), typeof(object), typeof(Type) });
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
il.Emit(FishTranspiler.Argument(0)); //Category
|
||||
il.Emit(FishTranspiler.Call(gUIController_Tab!)); //Tab(Category)
|
||||
il.Emit(FishTranspiler.Field(tab_entries!)); //tab.entries
|
||||
/*il.Emit(FishTranspiler.Argument(2)); //type
|
||||
il.Emit(FishTranspiler.Call(memberInfo_Name)); //type.Name
|
||||
il.Emit(FishTranspiler.LoadConstant(4)); //Category.Modder
|
||||
il.Emit(FishTranspiler.Argument(2)); //type
|
||||
il.Emit(FishTranspiler.LoadConstant(0)); //closeable = false
|
||||
il.Emit(FishTranspiler.LoadConstant(1)); //dynGen = true
|
||||
il.Emit(FishTranspiler.Call(entry_Create)); //Create(string, Category, Type, bool, bool)*/
|
||||
il.Emit(FishTranspiler.Argument(1)); //entry
|
||||
il.Emit(FishTranspiler.Argument(2)); //type
|
||||
il.Emit(FishTranspiler.Call(dictionary_Add!)); //entries.Add(Entry, Type)
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (Action<int, object, Type>)dynamicMethod.CreateDelegate(typeof(Action<int, object, Type>));
|
||||
}
|
||||
|
||||
public static Func<string, int, Type, bool, bool, object> Entry_Create => _entry_Create
|
||||
??= (Func<string, int, Type, bool, bool, object>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Entry", "Create").CreateDelegate(typeof(Func<string, int, Type, bool, bool, object>));
|
||||
private static Func<string, int, Type, bool, bool, object>? _entry_Create;
|
||||
|
||||
//Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality
|
||||
public static class XmlParser
|
||||
{
|
||||
public static void CollectXmlData()
|
||||
{
|
||||
foreach (var dir in ModLister.AllActiveModDirs)
|
||||
{
|
||||
var xmlFiles = dir.GetFiles("DubsAnalyzer.xml");
|
||||
if (xmlFiles.Length != 0)
|
||||
{
|
||||
foreach (var file in xmlFiles)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(file.OpenRead());
|
||||
|
||||
Parse(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterates through each child element in the document and attempts to extract method(s) from the strings inside the children
|
||||
private static void Parse(XmlDocument doc)
|
||||
=> ForEach(doc.DocumentElement.ChildNodes, node =>
|
||||
{
|
||||
var methods = new HashSet<MethodInfo>();
|
||||
var tip = string.Empty;
|
||||
var category = (int)Category.Modder;
|
||||
ForEach(node.ChildNodes, child =>
|
||||
{
|
||||
switch (child.Name.ToLowerInvariant())
|
||||
{
|
||||
case "category":
|
||||
category = (int)Enum.Parse(typeof(Category), child.InnerText, true); //child.InnerText;
|
||||
break;
|
||||
case "tooltip":
|
||||
case "tip":
|
||||
case "description":
|
||||
tip += child.InnerText;
|
||||
break;
|
||||
case "methods":
|
||||
case "method":
|
||||
AddMethodsFromChildNodes(child, methods, ParseMethod);
|
||||
break;
|
||||
case "derivedmethods":
|
||||
AddMethodsFromChildNodes(child, methods, ParseDerivedMethods);
|
||||
break;
|
||||
case "types":
|
||||
case "type":
|
||||
AddMethodsFromChildNodes(child, methods, ParseTypeMethods);
|
||||
break;
|
||||
case "derivedtypes":
|
||||
AddMethodsFromChildNodes(child, methods, ParseDerivedTypeMethods);
|
||||
break;
|
||||
case "nestedtype":
|
||||
case "nestedtypes":
|
||||
AddMethodsFromChildNodes(child, methods, ParseNestedTypeMethods);
|
||||
break;
|
||||
default:
|
||||
ThreadSafeLogger_Error($"[Analyzer] Attempting to read unknown value from a DubsAnalyzer.xml, the given input was {child.Name}, it should have been either 'methods', 'types', 'derivedMethods', 'derivedTypes', 'nestedTypes', 'category' or 'tooltip'");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var type = DynamicTypeBuilder_CreateType(node.Name, methods);
|
||||
var entry = Entry_Create(node.Name, category, type, false, true);
|
||||
if (tip != string.Empty)
|
||||
Entry_tip(entry) = tip;
|
||||
GUIController_AddSpecificEntry(category, entry, type);
|
||||
});
|
||||
|
||||
private static void AddMethodsFromChildNodes(XmlNode node, HashSet<MethodInfo> methods, Func<string, IEnumerable<MethodInfo>> func)
|
||||
=> ForEach(node.ChildNodes, childNode
|
||||
=> methods.AddRange(func(childNode.InnerText)));
|
||||
|
||||
private static void ForEach(XmlNodeList nodes, Action<XmlNode> action)
|
||||
{
|
||||
foreach (XmlNode node in nodes)
|
||||
{
|
||||
if (node.NodeType != XmlNodeType.Comment)
|
||||
action(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseDerivedMethods(string str)
|
||||
{
|
||||
foreach (var method in ParseMethod(str))
|
||||
{
|
||||
var subClasses = method.DeclaringType.AllSubclasses();
|
||||
|
||||
foreach (var subClass in subClasses)
|
||||
{
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared), m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
if (overridingMethod is null || overridingMethod.IsAbstract)
|
||||
continue;
|
||||
|
||||
yield return overridingMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseTypeMethods(string str) => Utility_GetTypeMethods(AccessTools.TypeByName(str), false);
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseNestedTypeMethods(string str)
|
||||
{
|
||||
var type = AccessTools.TypeByName(str);
|
||||
foreach (var subType in type.GetNestedTypes())
|
||||
{
|
||||
foreach (var method in Utility_GetTypeMethods(subType, false))
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseDerivedTypeMethods(string str)
|
||||
{
|
||||
var type = AccessTools.TypeByName(str);
|
||||
foreach (var subType in type.AllSubclasses())
|
||||
{
|
||||
foreach (var method in Utility_GetTypeMethods(subType, false))
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public class ByIndex<T_out> : IList<T_out?>, ICollection
|
||||
{
|
||||
private static ByIndex<T_out> _get = new();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_out>? _getThreadStatic;
|
||||
|
||||
public static ByIndex<T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_out>>();
|
||||
|
||||
public static ByIndex<T_out> GetDirectly => _get;
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
protected T_out?[] _items = Array.Empty<T_out>();
|
||||
protected object? _syncRoot;
|
||||
|
||||
public T_out? this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
ResizeIfNecessary(index);
|
||||
return _items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
ResizeIfNecessary(index);
|
||||
_items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[return: MaybeNull]
|
||||
public ref T_out GetReference(int index)
|
||||
{
|
||||
ResizeIfNecessary(index);
|
||||
return ref _items[index];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(int index, out T_out? value)
|
||||
{
|
||||
value = this[index];
|
||||
return !value!.Equals<T_out>(default!);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void ResizeIfNecessary(int checkedIndex)
|
||||
{
|
||||
if (checkedIndex >= _items.Length)
|
||||
ResizeFor(checkedIndex);
|
||||
}
|
||||
|
||||
protected void ResizeFor(int index)
|
||||
{
|
||||
Array.Resize(ref _items, Mathf.NextPowerOfTwo(index + 1));
|
||||
ResizeIfNecessary(index); // just in case
|
||||
}
|
||||
|
||||
public int Count => _items.Length;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
object ICollection.SyncRoot
|
||||
=> _syncRoot
|
||||
??= Interlocked.CompareExchange<object>(ref _syncRoot!, new(), null!);
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
[DoesNotReturn]
|
||||
public void Add(T_out? item) => throw new NotSupportedException();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_items.Length > 0)
|
||||
Array.Clear(_items, 0, _items.Length);
|
||||
}
|
||||
|
||||
public bool Contains(T_out? item) => Array.IndexOf(_items, item) >= 0;
|
||||
|
||||
public void CopyTo(T_out?[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<T_out?> GetEnumerator() => _items.AsEnumerable().GetEnumerator();
|
||||
|
||||
public int IndexOf(T_out? item) => Array.IndexOf(_items, item);
|
||||
|
||||
public void Insert(int index, T_out? item) => this[index] = item;
|
||||
|
||||
[DoesNotReturn]
|
||||
public bool Remove(T_out? item) => throw new NotSupportedException();
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < Count)
|
||||
_items[index] = default;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
|
||||
void ICollection.CopyTo(Array array, int index) => _items.CopyTo(array, index);
|
||||
}
|
||||
|
||||
public class ByIndex<T_in, T_out> : ByIndex<T_out>
|
||||
where T_in : notnull
|
||||
{
|
||||
private static ByIndex<T_in, T_out> _get = new();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_in, T_out>? _getThreadStatic;
|
||||
|
||||
public new static ByIndex<T_in, T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_in, T_out>>();
|
||||
|
||||
public new static ByIndex<T_in, T_out> GetDirectly => _get;
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
public unsafe T_out? this[T_in key]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[FunctionPointers.IndexGetter<T_in>.Default(key)];
|
||||
set => this[FunctionPointers.IndexGetter<T_in>.Default(key)] = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(T_in key, [NotNullWhen(true)] out T_out? value)
|
||||
{
|
||||
value = this[key];
|
||||
return !value!.Equals<T_out>(default!);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[return: MaybeNull]
|
||||
public unsafe ref T_out GetReference(T_in key)
|
||||
=> ref GetReference(FunctionPointers.IndexGetter<T_in>.Default(key))!;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seems slower than the dictionary based Cache.ByInt.
|
||||
/// </summary>
|
||||
public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_second, T_out>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
{
|
||||
private static ByIndex<T_first, T_second, T_out> _get = new();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_first, T_second, T_out>? _getThreadStatic;
|
||||
|
||||
public new static ByIndex<T_first, T_second, T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
|
||||
|
||||
public new static ByIndex<T_first, T_second, T_out> GetDirectly => _get;
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
public T_out? this[T_first first, T_second second]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => GetInner(first)[second];
|
||||
set => GetInner(first)[second] = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe ByIndex<T_second, T_out> GetInner(T_first first)
|
||||
{
|
||||
var firstIndex = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
ResizeIfNecessary(firstIndex);
|
||||
|
||||
ref var inner = ref _items[firstIndex];
|
||||
inner ??= new();
|
||||
|
||||
return inner!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[return: MaybeNull]
|
||||
public ref T_out GetReference(T_first first, T_second second)
|
||||
=> ref GetInner(first).GetReference(second)!;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(T_first first, T_second second, [NotNullWhen(true)] out T_out? value)
|
||||
{
|
||||
value = this[first, second];
|
||||
return !value!.Equals<T_out>(default!);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public struct ByInt<T_in, T_result>
|
||||
where T_in : notnull
|
||||
where T_result : IIsRefreshable<T_in, T_result>
|
||||
{
|
||||
private static Dictionary<ByInt<T_in, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByInt<T_in, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByInt<T_in, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_in, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByInt<T_in, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByInt() => Utility.All.Add(_get);
|
||||
|
||||
public int Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T_in key) => Key = FunctionPointers.IndexGetter<T_in>.Default(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T_result GetValue(T_in key)
|
||||
{
|
||||
ref var cache = ref Get.TryGetReferenceUnsafe(new(key));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[new(key)] = Reflection.New<T_result>();
|
||||
cache = ref Get.GetReference(new(key))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[new(key)] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int key)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int key, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(T_in key, [MaybeNullWhen(false)] out T_result value)
|
||||
=> Get.TryGetValue(new(key), out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(T_in key)
|
||||
{
|
||||
var cache = Get.TryGetValue(new(key));
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[new(key)] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByIntRefreshable<T_first, T_second, T_result> : IEquatable<ByIntRefreshable<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
where T_result : IIsRefreshable<ByIntRefreshable<T_first, T_second, T_result>, T_result>
|
||||
{
|
||||
private static EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>> _comparer = new Comparer();
|
||||
private class Comparer : EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(ByIntRefreshable<T_first, T_second, T_result> x, ByIntRefreshable<T_first, T_second, T_result> y)
|
||||
=> x.First == y.First
|
||||
&& x.Second == y.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(ByIntRefreshable<T_first, T_second, T_result> obj)
|
||||
=> HashCode.Combine(obj.First, obj.Second);
|
||||
}
|
||||
|
||||
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> _get = new(_comparer);
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>>(_comparer);
|
||||
|
||||
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByIntRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByIntRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
// CS8347 without this crap
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByIntRefreshable<T_first, T_second, T_result> key, [MaybeNullWhen(false)] out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByIntRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByIntRefreshable(T_first first, T_second second)
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByIntRefreshable(int first, int second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
|
||||
public int First;
|
||||
public int Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByIntRefreshable<T_first, T_second, T_result> other)
|
||||
=> First == other.First
|
||||
&& Second == other.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByIntRefreshable<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByInt<T_first, T_second, T_third, T_result> : IEquatable<ByInt<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
where T_result : IIsRefreshable<ByInt<T_first, T_second, T_third, T_result>, T_result>
|
||||
{
|
||||
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByInt() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ByInt<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first, int third)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByInt<T_first, T_second, T_third, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByInt<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T_first first, T_second second, T_third third)
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
|
||||
Third = FunctionPointers.IndexGetter<T_third>.Default(third);
|
||||
}
|
||||
|
||||
public int First;
|
||||
public int Second;
|
||||
public int Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T_first, T_second, T_third, T_result> other)
|
||||
=> First == other.First
|
||||
&& Second == other.Second
|
||||
&& Third == other.Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second, Third);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByInt<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public static class ByMap<T> where T : new()
|
||||
{
|
||||
private static List<(Map map, T value)> _cache = new();
|
||||
[ThreadStatic]
|
||||
private static List<(Map map, T value)>? _cacheThreadStatic;
|
||||
|
||||
public static List<(Map map, T value)> GetCache
|
||||
=> UnityData.IsInMainThread ? _cache
|
||||
: _cacheThreadStatic ??= Utility.AddNew<List<(Map map, T value)>>();
|
||||
|
||||
public static List<(Map map, T value)> GetCacheDirectly => _cache;
|
||||
|
||||
static ByMap() => Utility.All.Add(_cache);
|
||||
|
||||
private static Func<Map, T> Initialize { get; }
|
||||
= typeof(MapComponent).IsAssignableFrom(typeof(T))
|
||||
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent), Array.Empty<Type>(), new[] { typeof(T) }))
|
||||
// missing T constraints cause the compiler to complain when trying to just use map => map.GetComponent<T>
|
||||
: _ => new();
|
||||
|
||||
public static T GetFor(Map map)
|
||||
{
|
||||
var cache = GetCache;
|
||||
for (var i = 0; i < cache.Count; i++)
|
||||
{
|
||||
var cacheEntry = cache[i];
|
||||
if (cacheEntry.map == map)
|
||||
return cacheEntry.value;
|
||||
|
||||
if (Find.Maps.Count <= i + 1
|
||||
&& !Find.Maps.Contains(cacheEntry.map))
|
||||
{
|
||||
cache.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
cache.Add((map, Initialize(map)));
|
||||
return cache[^1].value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public static class ByReference<T_first, T_result>
|
||||
where T_first : notnull
|
||||
{
|
||||
private static Dictionary<T_first, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<T_first, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<T_first, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<T_first, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<T_first, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByReference<T_first, T_second, T_result> : IEquatable<ByReference<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
{
|
||||
private static Dictionary<ByReference<T_first, T_second, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReference<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_result>, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T_first, T_second, T_result>, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByReference(T_first first, T_second second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<ByReference<T_first, T_second, T_result>>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T_first, T_second, T_result>>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public T_first First;
|
||||
|
||||
public T_second Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReference<T_first, T_second, T_result> other)
|
||||
=> First.Equals<T_first>(other.First)
|
||||
&& Second.Equals<T_second>(other.Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReference<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByReference<T_first, T_second, T_result> left, ByReference<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByReference<T_first, T_second, T_result> left, ByReference<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReference<T_first, T_second, T_third, T_result> : IEquatable<ByReference<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
{
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
/*UnityData.IsInMainThread ? _get
|
||||
:*/ => _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByReference(T_first first, T_second second, T_third third)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Third = third;
|
||||
_hashCode = HashCode.Combine(first, second, third);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<ByReference<T_first, T_second, T_third, T_result>>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T_first, T_second, T_third, T_result>>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_second, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public T_first First;
|
||||
|
||||
public T_second Second;
|
||||
|
||||
public T_third Third;
|
||||
|
||||
private int _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReference<T_first, T_second, T_third, T_result> other)
|
||||
=> First.Equals<T_first>(other.First)
|
||||
&& Second.Equals<T_second>(other.Second)
|
||||
&& Third.Equals<T_third>(other.Third);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReference<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByReference<T_first, T_second, T_third, T_result> left, ByReference<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByReference<T_first, T_second, T_third, T_result> left, ByReference<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReference<T_first, T_second, T_third, T_fourth, T_result> : IEquatable<ByReference<T_first, T_second, T_third, T_fourth, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull where T_fourth : notnull
|
||||
{
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
/*UnityData.IsInMainThread ? _get
|
||||
:*/ => _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByReference(T_first first, T_second second, T_third third, T_fourth fourth)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Third = third;
|
||||
Fourth = fourth;
|
||||
_hashCode = HashCode.Combine(first, second, third, fourth);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_fourth>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<ByReference<T_first, T_second, T_third, T_fourth, T_result>>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_fourth>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T_first, T_second, T_third, T_fourth, T_result>>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_second, T_third, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public T_first First;
|
||||
|
||||
public T_second Second;
|
||||
|
||||
public T_third Third;
|
||||
|
||||
public T_fourth Fourth;
|
||||
|
||||
private int _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReference<T_first, T_second, T_third, T_fourth, T_result> other)
|
||||
=> First.Equals<T_first>(other.First)
|
||||
&& Second.Equals<T_second>(other.Second)
|
||||
&& Third.Equals<T_third>(other.Third)
|
||||
&& Fourth.Equals<T_fourth>(other.Fourth);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReference<T_first, T_second, T_third, T_fourth, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByReference<T_first, T_second, T_third, T_fourth, T_result> left, ByReference<T_first, T_second, T_third, T_fourth, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByReference<T_first, T_second, T_third, T_fourth, T_result> left, ByReference<T_first, T_second, T_third, T_fourth, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public static class ByReferenceRefreshable<T_first, T_result>
|
||||
where T_first : notnull
|
||||
where T_result : IIsRefreshable<T_first, T_result>
|
||||
{
|
||||
private static Dictionary<T_first, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<T_first, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<T_first, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<T_first, T_result>>();
|
||||
|
||||
public static Dictionary<T_first, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T_result GetValue(T_first key)
|
||||
{
|
||||
ref var cache = ref Get.TryGetReferenceUnsafe(key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Get.GetReference(key)!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(T_first key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(T_first key)
|
||||
{
|
||||
var cache = Get.TryGetValue(key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByReferenceRefreshable<T_first, T_second, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>
|
||||
{
|
||||
internal ByReference<T_first, T_second, T_result> _innerValue;
|
||||
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
// CS8347 without this crap
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ByReferenceRefreshable(T_first first, T_second second) => _innerValue = new(first, second);
|
||||
|
||||
public T_first First => _innerValue.First;
|
||||
|
||||
public T_second Second => _innerValue.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_result> other)
|
||||
=> _innerValue.Equals(other._innerValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _innerValue.GetHashCode();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReferenceRefreshable<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReferenceRefreshable<T_first, T_second, T_third, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>
|
||||
{
|
||||
private ByReference<T_first, T_second, T_third, T_result> _innerValue;
|
||||
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Activator.CreateInstance<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Activator.CreateInstance<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ByReferenceRefreshable(T_first first, T_second second, T_third third)
|
||||
=> _innerValue = new(first, second, third);
|
||||
|
||||
public T_first First => _innerValue.First;
|
||||
public T_second Second => _innerValue.Second;
|
||||
public T_third Third => _innerValue.Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_third, T_result> other)
|
||||
=> _innerValue.Equals(other._innerValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _innerValue.GetHashCode();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReferenceRefreshable<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public interface IIsRefreshable<T, V> where V : IIsRefreshable<T, V>
|
||||
{
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public V SetNewValue(T key);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 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.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public static class Utility
|
||||
{
|
||||
public static ConcurrentBag<ICollection> All { get; } = new();
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
foreach (var cache in All)
|
||||
{
|
||||
cache.GetType()
|
||||
.GetMethods()
|
||||
.Where(m
|
||||
=> m.Name == nameof(Array.Clear)
|
||||
&& m.GetParameters().Length == 0)
|
||||
.First()
|
||||
.Invoke(cache, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static T AddNew<T>() where T : ICollection, new()
|
||||
{
|
||||
var newCollection = new T();
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
public static T AddNew<T>(IEqualityComparer comparer) where T : ICollection, new()
|
||||
{
|
||||
var newCollection = (T)Activator.CreateInstance(typeof(T), comparer);
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int IndexGetter<T>(T item);
|
||||
@@ -0,0 +1,645 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PerformanceFish.Collections;
|
||||
|
||||
#pragma warning disable CS8766, CS8767
|
||||
public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>
|
||||
{
|
||||
//private int[] _buckets;
|
||||
|
||||
internal Entry[] _entries;
|
||||
|
||||
private int _count;
|
||||
|
||||
private int _version;
|
||||
|
||||
//private unsafe delegate*<TKey, int> _keyGetHashCode = FunctionPointers.GetHashCode<TKey>.Default;
|
||||
//private unsafe delegate*<TKey, TKey, bool> _keyEquals = FunctionPointers.Equals<TKey>.Default;
|
||||
|
||||
//private int _freeList;
|
||||
|
||||
//private int _freeCount;
|
||||
|
||||
//private KeyCollection _keys;
|
||||
|
||||
//private ValueCollection _values;
|
||||
|
||||
public ICollection<TKey> Keys => throw new NotImplementedException();
|
||||
|
||||
public ICollection<TValue> Values => throw new NotImplementedException();
|
||||
|
||||
public int Version
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _count;
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
ICollection IDictionary.Keys => throw new NotImplementedException();
|
||||
|
||||
ICollection IDictionary.Values => throw new NotImplementedException();
|
||||
|
||||
bool IDictionary.IsFixedSize => false;
|
||||
object ICollection.SyncRoot => this; // matching System.Collections.Generic.Dictionary
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => throw new NotImplementedException();
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => throw new NotImplementedException();
|
||||
|
||||
object? IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
=> key is TKey tkey
|
||||
? this[tkey]
|
||||
: ThrowHelper.ThrowKeyNotFoundException<object, TValue>(key);
|
||||
|
||||
set
|
||||
{
|
||||
if (key is TKey tkey)
|
||||
{
|
||||
if (value is TValue tvalue)
|
||||
this[tkey] = tvalue;
|
||||
else if (value is null)
|
||||
this[tkey] = default;
|
||||
else
|
||||
ThrowHelper.ThrowWrongValueTypeArgumentException(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowWrongKeyTypeArgumentException(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TValue? this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
}
|
||||
|
||||
if (entry.Next is null)
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
ref var reference = ref TryGetReferenceUnsafe(key);
|
||||
if (!Unsafe.IsNullRef(ref reference))
|
||||
{
|
||||
reference = value;
|
||||
_version++;
|
||||
return;
|
||||
}
|
||||
|
||||
Add(key, value!);
|
||||
}
|
||||
}
|
||||
|
||||
public FishTable() : this(0) { }
|
||||
|
||||
public FishTable(int minimumCapacity)
|
||||
=> _entries = new Entry[
|
||||
minimumCapacity <= 1 ? 1 // Minimum size to not throw on indexing into _entries. Length is not checked beforehand
|
||||
: Mathf.NextPowerOfTwo(minimumCapacity)]; // System.Collections.Dictionary starts at 3. So wasteful.
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static uint FastModulo(uint first, uint second)
|
||||
=> first & (second - 1);
|
||||
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
{
|
||||
entry = entry.Next.Entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
value = entry.Value;
|
||||
return entry.Next != null;
|
||||
}
|
||||
|
||||
public TValue? TryGetValue(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
return default;
|
||||
}
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
|
||||
public ref TValue GetReference(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
}
|
||||
|
||||
if (entry.Next == null)
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
|
||||
return ref entry.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not.
|
||||
/// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns>ref TValue or Unsafe.NullRef{TValue}</returns>
|
||||
public ref TValue TryGetReferenceUnsafe(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<TValue>()!;
|
||||
}
|
||||
|
||||
return ref entry.Next == null
|
||||
? ref Unsafe.NullRef<TValue>()
|
||||
: ref entry.Value;
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return entry.Next != null;
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
if (_count == _entries.Length) // throwing DuplicateKeyExceptions happens later, so Expanding occurs based on expected
|
||||
Expand(); // behaviour instead of matching actual results like in System.Collections.Generic.
|
||||
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.Next != null)
|
||||
{
|
||||
if (entry.HashCode == keyCode && entry.Key.Equals<TKey>(key))
|
||||
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
|
||||
|
||||
if (entry.Next == EntryReference.Empty)
|
||||
break;
|
||||
|
||||
entry = ref entry.Next.Entry;
|
||||
}
|
||||
|
||||
if (entry.Next == null) // means the entry is empty
|
||||
entry = new(keyCode, key, value);
|
||||
else // if (entry.Next == _freeSlot) // occupied entry with an empty slot in entry.Next
|
||||
entry.Next = new(keyCode, key, value);
|
||||
// entry.Next != _freeSlot would be a threading issue, as this was just checked in the loop. Unhandled.
|
||||
|
||||
_count++;
|
||||
_version++;
|
||||
}
|
||||
|
||||
private void Expand()
|
||||
{
|
||||
var newLength = Mathf.NextPowerOfTwo(_entries.Length + 1);
|
||||
var newArray = new Entry[newLength];
|
||||
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Next is null)
|
||||
continue;
|
||||
|
||||
BeforeAdding:
|
||||
ref var newEntry = ref newArray[FastModulo(entry.HashCode, (uint)newLength)];
|
||||
|
||||
while (newEntry.Next != null)
|
||||
{
|
||||
if (newEntry.HashCode == entry.HashCode && newEntry.Key.Equals<TKey>(entry.Key))
|
||||
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry.Key);
|
||||
|
||||
if (newEntry.Next == EntryReference.Empty)
|
||||
break;
|
||||
|
||||
newEntry = ref newEntry.Next.Entry;
|
||||
}
|
||||
|
||||
var nextEntry = entry.Next;
|
||||
entry.Next = EntryReference.Empty;
|
||||
|
||||
if (newEntry.Next == null)
|
||||
newEntry = entry;
|
||||
else
|
||||
newEntry.Next = new(entry);
|
||||
|
||||
if (nextEntry != EntryReference.Empty)
|
||||
{
|
||||
entry = nextEntry.Entry;
|
||||
goto BeforeAdding;
|
||||
}
|
||||
}
|
||||
|
||||
_entries = newArray;
|
||||
}
|
||||
|
||||
private ref Entry TryFindEntry(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<Entry>();
|
||||
}
|
||||
|
||||
return ref entry;
|
||||
}
|
||||
|
||||
private void RemoveEntry(ref Entry entry)
|
||||
{
|
||||
entry = entry.Next != EntryReference.Empty && entry.Next != null
|
||||
? entry.Next.Entry
|
||||
: default;
|
||||
|
||||
_count--;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
ref var entry = ref TryFindEntry(key);
|
||||
if (Unsafe.IsNullRef(ref entry))
|
||||
return false;
|
||||
|
||||
RemoveEntry(ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(_entries, 0, _entries.Length);
|
||||
_count = 0;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
=> TryGetValue(item.Key, out var value)
|
||||
&& value.Equals<TValue>(item.Value);
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
var length = _entries.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Next == null)
|
||||
continue;
|
||||
|
||||
array[arrayIndex] = new(entry.Key, entry.Value);
|
||||
arrayIndex++;
|
||||
|
||||
while (entry.Next != EntryReference.Empty)
|
||||
{
|
||||
entry = entry.Next.Entry;
|
||||
array[arrayIndex] = new(entry.Key, entry.Value);
|
||||
arrayIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
ref var entry = ref TryFindEntry(item.Key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref entry)
|
||||
|| !entry.Value.Equals<TValue>(item.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveEntry(ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
=> new(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
return key is TKey tkey
|
||||
&& ContainsKey(tkey);
|
||||
}
|
||||
|
||||
void IDictionary.Add(object key, object value) => throw new NotImplementedException();
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.DICT_ENTRY);
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
if (key is TKey tkey)
|
||||
Remove(tkey);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index) => throw new NotImplementedException();
|
||||
|
||||
internal struct Entry
|
||||
{
|
||||
public uint HashCode;
|
||||
public TKey Key;
|
||||
public EntryReference Next;
|
||||
public TValue Value;
|
||||
|
||||
internal Entry(uint hashCode, TKey key, TValue value)
|
||||
{
|
||||
HashCode = hashCode;
|
||||
Key = key;
|
||||
Value = value;
|
||||
Next = EntryReference.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal class EntryReference
|
||||
{
|
||||
public static readonly EntryReference Empty = new();
|
||||
|
||||
public Entry Entry;
|
||||
|
||||
public EntryReference(uint hashCode, TKey key, TValue value) => Entry = new(hashCode, key, value);
|
||||
|
||||
public EntryReference(Entry entry) => Entry = entry;
|
||||
|
||||
private EntryReference() { }
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator
|
||||
{
|
||||
private readonly FishTable<TKey, TValue> _dictionary;
|
||||
private readonly int _version;
|
||||
private int _index;
|
||||
private EntryReference _next;
|
||||
private KeyValuePair<TKey, TValue> _current;
|
||||
private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
|
||||
|
||||
internal const int DICT_ENTRY = 1;
|
||||
internal const int KEY_VALUE_PAIR = 2;
|
||||
|
||||
internal Enumerator(FishTable<TKey, TValue> dictionary, int getEnumeratorRetType)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
_version = dictionary._version;
|
||||
_index = 0;
|
||||
_getEnumeratorRetType = getEnumeratorRetType;
|
||||
_current = default;
|
||||
_next = EntryReference.Empty;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_version != _dictionary._version)
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
||||
|
||||
if (_next != EntryReference.Empty)
|
||||
{
|
||||
_current = new(_next.Entry.Key, _next.Entry.Value);
|
||||
_next = _next.Entry.Next;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
|
||||
// dictionary.count+1 could be negative if dictionary.count is int.MaxValue
|
||||
while ((uint)_index < (uint)_dictionary._entries.Length)
|
||||
{
|
||||
ref var entry = ref _dictionary._entries[_index++];
|
||||
|
||||
if (entry.Next != null)
|
||||
{
|
||||
_current = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
_next = entry.Next;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_index = _dictionary._entries.Length + 1;
|
||||
_current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeyValuePair<TKey, TValue> Current => _current;
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
object? IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _getEnumeratorRetType == DICT_ENTRY
|
||||
? new DictionaryEntry(_current.Key, _current.Value)
|
||||
: new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
if (_version != _dictionary._version)
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
||||
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
DictionaryEntry IDictionaryEnumerator.Entry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return new DictionaryEntry(_current.Key, _current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
object? IDictionaryEnumerator.Key
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _current.Key;
|
||||
}
|
||||
}
|
||||
|
||||
object? IDictionaryEnumerator.Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _current.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThrowHelper
|
||||
{
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
|
||||
=> throw new InvalidOperationException("Collection was modified; enumeration operation may not execute.");
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
|
||||
=> throw new InvalidOperationException("Enumeration has either not started or has already finished.");
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static V ThrowKeyNotFoundException<T, V>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
return default;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException(object key)
|
||||
=> throw new KeyNotFoundException(
|
||||
string.Format("The given key '{0}' was not present in the dictionary.", key));
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowWrongValueTypeArgumentException(object value)
|
||||
=> throw new ArgumentException(
|
||||
string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.",
|
||||
value, typeof(TValue)),
|
||||
nameof(value));
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowWrongKeyTypeArgumentException(object? key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
throw new ArgumentException(
|
||||
string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.",
|
||||
key, typeof(TKey)),
|
||||
nameof(key));
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowAddingDuplicateWithKeyArgumentException((object)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowAddingDuplicateWithKeyArgumentException(object key)
|
||||
=> throw new ArgumentException(
|
||||
string.Format("An item with the same key has already been added. Key: {0}", key));
|
||||
}
|
||||
}
|
||||
public static class Extensions
|
||||
{
|
||||
public static ref TValue TryGetReferenceUnsafe<TKey, TValue>(this FishTable<TKey, TValue> fishTable, ref TKey key)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(ref key);
|
||||
ref var entry = ref fishTable._entries[FishTable<TKey, TValue>.FastModulo(keyCode, (uint)fishTable._entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals(ref key))
|
||||
{
|
||||
if (entry.Next != FishTable<TKey, TValue>.EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<TValue>()!;
|
||||
}
|
||||
|
||||
return ref entry.Next == null
|
||||
? ref Unsafe.NullRef<TValue>()
|
||||
: ref entry.Value;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS8766, CS8767
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2022 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.Diagnostics;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
/// <summary>
|
||||
/// Log Functions that only do something when compiling in Debug Mode
|
||||
/// </summary>
|
||||
public static class DebugLog
|
||||
{
|
||||
[Conditional("DEBUG")]
|
||||
public static void Message(string text) => Log.Message(text);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Warning(string text) => Log.Warning(text);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Error(string text) => Log.Error(text);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
#if V1_3
|
||||
//mostly integrated into vanilla with 1.4
|
||||
|
||||
using System.Linq;
|
||||
using AllDesignationsOnCache = PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.Thing, PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.Thing>>;
|
||||
using DesignationsOfDefCache = PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.DesignationDef, PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.DesignationDef>>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class DesignationManagerCaching : ClassWithFishPatches
|
||||
{
|
||||
public class SpawnedDesignationsOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.SpawnedDesignationsOfDef(null);
|
||||
public static bool Prefix(DesignationManager __instance, DesignationDef def, ref IEnumerable<Designation> __result, out bool __state)
|
||||
{
|
||||
if (def.GetType() != typeof(DesignationDef)) // different types have different indexing. Can't use those
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DesignationsOfDefCache.TryGetValue(new(__instance, def), out var value))
|
||||
return __state = true;
|
||||
|
||||
__result = value.designations;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(DesignationManager __instance, DesignationDef def, bool __state, IEnumerable<Designation> __result)
|
||||
{
|
||||
if (__state)
|
||||
RefreshDesignationCache(DesignationsOfDefCache.Get, __instance, def, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RefreshDesignationCache<T>(/*Cache.ByIndex<DesignationManager, T, DesignationCache<T>>*/
|
||||
Dictionary<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>> dict, DesignationManager instance, T key, IEnumerable<Designation> result)
|
||||
where T : notnull
|
||||
{
|
||||
if (dict.TryGetValue(new(instance, key), out var cache))
|
||||
cache.UpdateList(result);
|
||||
else
|
||||
cache.Initialize(result, instance);
|
||||
|
||||
dict[new(instance, key)] = cache;
|
||||
}
|
||||
|
||||
public class AnySpawnedDesignationOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.AnySpawnedDesignationOfDef(null);
|
||||
public static bool AnySpawnedDesignationOfDef(DesignationManager __instance, DesignationDef def) => __instance.SpawnedDesignationsOfDef(def).Any();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(AnySpawnedDesignationOfDef);
|
||||
}
|
||||
|
||||
public class AllDesignationsOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.AllDesignationsOn(null);
|
||||
public static bool Prefix(DesignationManager __instance, Thing t, ref IEnumerable<Designation> __result, out bool __state)
|
||||
{
|
||||
if (!AllDesignationsOnCache.TryGetValue(new(__instance, t), out var value))
|
||||
return __state = true;
|
||||
|
||||
__result = value.designations;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(DesignationManager __instance, Thing t, bool __state, IEnumerable<Designation> __result)
|
||||
{
|
||||
if (__state)
|
||||
RefreshDesignationCache(AllDesignationsOnCache.Get, __instance, t, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class DesignationOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.DesignationOn(null);
|
||||
public static Designation DesignationOn(DesignationManager __instance, Thing t) => __instance.AllDesignationsOn(t).FirstOrDefault();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(DesignationOn);
|
||||
}
|
||||
|
||||
public class DesignationOn_ByDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.DesignationOn(null, null);
|
||||
public static Designation? DesignationOn(DesignationManager __instance, Thing t, DesignationDef def) => FirstDesignationOfDef(__instance.AllDesignationsOn(t), def);
|
||||
public static Designation? FirstDesignationOfDef(IEnumerable<Designation> designations, DesignationDef def)
|
||||
{
|
||||
if (designations is not List<Designation> designationList)
|
||||
return Fallback(designations, def);
|
||||
|
||||
for (var i = 0; i < designationList.Count; i++)
|
||||
{
|
||||
var designation = designationList[i];
|
||||
if (designation.def == def)
|
||||
return designation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static Designation? Fallback(IEnumerable<Designation> designations, DesignationDef def)
|
||||
{
|
||||
foreach (var designation in designations)
|
||||
{
|
||||
if (designation.def == def)
|
||||
return designation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(DesignationOn);
|
||||
}
|
||||
|
||||
public class HasMapDesignationOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.HasMapDesignationOn(null);
|
||||
public static bool HasMapDesignationOn(DesignationManager __instance, Thing t) => __instance.AllDesignationsOn(t).Any();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(HasMapDesignationOn);
|
||||
}
|
||||
|
||||
public struct DesignationCache<T> : Cache.IIsRefreshable<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>>
|
||||
where T : notnull
|
||||
{
|
||||
public IEnumerable<Designation> designations;
|
||||
private int _nextRefreshTick;
|
||||
private int _managerListState;
|
||||
private DesignationManager _manager;
|
||||
|
||||
public void Initialize(IEnumerable<Designation> designations, DesignationManager manager)
|
||||
{
|
||||
this.designations = new List<Designation>(designations);
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_manager = manager;
|
||||
_managerListState = manager.allDesignations.Version();
|
||||
}
|
||||
public void UpdateList(IEnumerable<Designation> designations)
|
||||
{
|
||||
var cachedList = (List<Designation>)this.designations;
|
||||
cachedList.Clear();
|
||||
cachedList.AddRange(designations);
|
||||
ShouldRefreshNow = false;
|
||||
_managerListState = _manager.allDesignations.Version();
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame || _managerListState != _manager.allDesignations.Version();
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public DesignationCache<T> SetNewValue(Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>> key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2022 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;
|
||||
|
||||
namespace PerformanceFish;
|
||||
[StaticConstructorOnStartup]
|
||||
public static class FishDefOfOrNull
|
||||
{
|
||||
public static RecipeDef VFEA_MendWeapon { get; }
|
||||
public static RecipeDef VFEA_MendArmor { get; }
|
||||
public static RecipeDef VFEA_MendApparel { get; }
|
||||
|
||||
static FishDefOfOrNull()
|
||||
{
|
||||
foreach (var property in typeof(FishDefOfOrNull).GetProperties())
|
||||
{
|
||||
typeof(FishDefOfOrNull).GetFields(BindingFlags.Static | BindingFlags.NonPublic).First(f => f.Name.Contains(property.Name)).SetValue(null,
|
||||
typeof(FishDefOfOrNull).GetMethod(nameof(DefOf), BindingFlags.Static | BindingFlags.NonPublic)
|
||||
.MakeGenericMethod(property.GetMethod.ReturnType).Invoke(null, new[] { property.Name }));
|
||||
}
|
||||
}
|
||||
private static T DefOf<T>(string name) where T : Def => DefDatabase<T>.GetNamedSilentFail(name);
|
||||
}*/
|
||||
@@ -0,0 +1,399 @@
|
||||
// Copyright (c) 2022 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.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasDescription
|
||||
{
|
||||
public virtual bool ShouldBenchmark => false;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
if (value)
|
||||
{
|
||||
if (ShouldBePatched)
|
||||
TryPatch();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shouldBePatched = ShouldBePatched;
|
||||
TryUnpatch();
|
||||
ShouldBePatched = shouldBePatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _enabled = true;
|
||||
public virtual bool DefaultState => true;
|
||||
public virtual int PrefixMethodPriority => TryGetPriority(PrefixMethodInfo);
|
||||
public virtual int PostfixMethodPriority => TryGetPriority(PostfixMethodInfo);
|
||||
public virtual int TranspilerMethodPriority => TryGetPriority(TranspilerMethodInfo);
|
||||
public virtual int FinalizerMethodPriority => TryGetPriority(FinalizerMethodInfo);
|
||||
public MethodInfo? HarmonyMethodInfo => HarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> HarmonyMethodInfos { get; } = new();
|
||||
public MethodInfo? BenchmarkHarmonyMethodInfo => BenchmarkHarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = new();
|
||||
private bool Patched { get; set; }
|
||||
private bool ShouldBePatched { get; set; }
|
||||
|
||||
public virtual string? Name => null;
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual Delegate? TargetMethodGroup => null;
|
||||
public virtual IEnumerable<Delegate>? TargetMethodGroups => null;
|
||||
public virtual Expression<Action>? TargetMethod => null;
|
||||
public virtual IEnumerable<Expression<Action>>? TargetMethods => null;
|
||||
public virtual MethodBase TargetMethodInfo
|
||||
=> TargetMethod != null
|
||||
? SymbolExtensions.GetMethodInfo(TargetMethod)
|
||||
: TargetMethodGroup?.Method
|
||||
?? throw new MissingMethodException(GetType().ToString());
|
||||
|
||||
public virtual IEnumerable<MethodBase> TargetMethodInfos
|
||||
=> _targetMethodInfos
|
||||
??= (TargetMethods?.Select(m
|
||||
=> SymbolExtensions.GetMethodInfo(m))
|
||||
?? TargetMethodGroups?.Select(m => m.Method)
|
||||
?? (TargetMethodInfo != null
|
||||
? new MethodBase[] { TargetMethodInfo }.AsEnumerable()
|
||||
: throw new MissingMethodException(GetType().ToString()))).ToArray();
|
||||
|
||||
private MethodBase[]? _targetMethodInfos;
|
||||
|
||||
public virtual MethodInfo ReversePatchMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "REVERSEPATCH" || (m.HasAttribute<HarmonyReversePatch>() && !m.HasAttribute<HarmonyTranspiler>()));
|
||||
public virtual MethodInfo ReversePatchTranspilerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "REVERSEPATCHTRANSPILER" || (m.HasAttribute<HarmonyTranspiler>() && m.HasAttribute<HarmonyReversePatch>()));
|
||||
public virtual MethodInfo PrefixMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "PREFIX" || m.HasAttribute<HarmonyPrefix>());
|
||||
public virtual MethodInfo PostfixMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "POSTFIX" || m.HasAttribute<HarmonyPostfix>());
|
||||
public virtual MethodInfo TranspilerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "TRANSPILER" || (m.HasAttribute<HarmonyTranspiler>() && !m.HasAttribute<HarmonyReversePatch>()));
|
||||
public virtual MethodInfo FinalizerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "FINALIZER" || m.HasAttribute<HarmonyFinalizer>());
|
||||
|
||||
internal bool IsBenchmarking => ShouldBenchmark && !FinishedBenchmarking;
|
||||
internal bool FinishedBenchmarking = false;
|
||||
|
||||
protected FishPatch() => _enabled = DefaultState;
|
||||
|
||||
public virtual MethodInfo? TryPatch()
|
||||
{
|
||||
if (!TargetMethodInfos?.Any() ?? true)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but it lacks a target method for patching.");
|
||||
return null;
|
||||
}
|
||||
if (PrefixMethodInfo is null && PostfixMethodInfo is null && TranspilerMethodInfo is null && FinalizerMethodInfo is null && ReversePatchMethodInfo is null)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but there are none. This is likely not intended.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ShouldBePatched = true;
|
||||
if (Enabled && !Patched)
|
||||
ApplyPatch();
|
||||
|
||||
return HarmonyMethodInfo;
|
||||
}
|
||||
|
||||
protected virtual void ApplyPatch()
|
||||
{
|
||||
Patched = true;
|
||||
foreach (var method in TargetMethodInfos!)
|
||||
{
|
||||
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
|
||||
|
||||
try
|
||||
{
|
||||
if (IsBenchmarking)
|
||||
{
|
||||
Patched = false;
|
||||
|
||||
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
||||
{
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(moveNext,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(method,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ReversePatchMethodInfo != null)
|
||||
HarmonyMethodInfos.Add(Harmony.ReversePatch(method, new(ReversePatchMethodInfo), ReversePatchTranspilerMethodInfo));
|
||||
|
||||
if (PrefixMethodInfo != null || PostfixMethodInfo != null || TranspilerMethodInfo != null || FinalizerMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(method,
|
||||
prefix: PrefixMethodInfo != null ? new(PrefixMethodInfo, PrefixMethodPriority) : null,
|
||||
postfix: PostfixMethodInfo != null ? new(PostfixMethodInfo, PostfixMethodPriority) : null,
|
||||
transpiler: TranspilerMethodInfo != null ? new(TranspilerMethodInfo, TranspilerMethodPriority) : null,
|
||||
finalizer: FinalizerMethodInfo != null ? new(FinalizerMethodInfo, FinalizerMethodPriority) : null));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to patch {method.FullDescription()} with {PrefixMethodInfo.FullDescription()}" +
|
||||
$", {PostfixMethodInfo.FullDescription()}, {TranspilerMethodInfo.FullDescription()}, {FinalizerMethodInfo.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void TryUnpatch()
|
||||
{
|
||||
ShouldBePatched = false;
|
||||
if (/*!Enabled &&*/ Patched)
|
||||
ApplyUnpatching();
|
||||
}
|
||||
|
||||
protected virtual void ApplyUnpatching()
|
||||
{
|
||||
Patched = false;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
if (PrefixMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, PrefixMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {PrefixMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (PostfixMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, PostfixMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {PostfixMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (TranspilerMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, TranspilerMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {TranspilerMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (FinalizerMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, FinalizerMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {FinalizerMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int TryGetPriority(MethodInfo info) => info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
||||
|
||||
public virtual void ExposeData()
|
||||
=> Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
}
|
||||
|
||||
internal static class Benchmarking
|
||||
{
|
||||
internal static int SwitchFrame = int.MaxValue;
|
||||
internal static int EndFrame = int.MaxValue;
|
||||
|
||||
public static void Prefix(MethodBase __originalMethod, out Results __state)
|
||||
{
|
||||
if (!Cache.ByReference<MethodBase, Results>.Get.TryGetValue(__originalMethod, out __state))
|
||||
Cache.ByReference<MethodBase, Results>.Get[__originalMethod] = __state = new(__originalMethod);
|
||||
|
||||
__state.Stopwatch.Start();
|
||||
}
|
||||
|
||||
public static void Postfix(Results __state)
|
||||
{
|
||||
__state.Stopwatch.Stop();
|
||||
__state.Count++;
|
||||
|
||||
//Log.Message($"SwitchFrame is {SwitchFrame}, EndFrame is {EndFrame}, CurrentFrame is {Time.frameCount}");
|
||||
|
||||
//if (__state.Stopwatch.ElapsedMilliseconds > 100 || __state.Count > 100000)
|
||||
if (__state.Switched ? GenTicks.TicksGame > EndFrame : GenTicks.TicksGame > SwitchFrame)
|
||||
PrintResultsAndStop(__state);
|
||||
}
|
||||
|
||||
public static void PrintResultsAndStop(Results results)
|
||||
{
|
||||
var ticks = results.Stopwatch.ElapsedTicks;
|
||||
Log.Message($"{results.Method.FullDescription()} took {ticks} ticks for {results.Count} calls. This equals {ticks * 1000L / results.Count} ticks per 1000 calls on average.");
|
||||
|
||||
results.Count = 0;
|
||||
results.Switched = true;
|
||||
results.Stopwatch.Reset();
|
||||
|
||||
var fishPatch = PerformanceFishMod.AllPatchClasses
|
||||
.SelectMany(patchClass
|
||||
=> patchClass.Patches)
|
||||
.First(patch
|
||||
=> patch.TargetMethodInfos.Contains(results.Method));
|
||||
|
||||
if (fishPatch.FinishedBenchmarking)
|
||||
{
|
||||
foreach (var target in fishPatch.TargetMethodInfos)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(target) is { } moveNext)
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Postfix));
|
||||
}
|
||||
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Postfix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fishPatch.FinishedBenchmarking = true;
|
||||
fishPatch.TryPatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class Results
|
||||
{
|
||||
public int Count;
|
||||
public Stopwatch Stopwatch;
|
||||
public MethodBase Method;
|
||||
public bool Switched;
|
||||
|
||||
public Results(MethodBase method)
|
||||
{
|
||||
Stopwatch = new();
|
||||
Method = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class FirstPriorityFishPatch : FishPatch
|
||||
{
|
||||
public override int PrefixMethodPriority => Priority.First;
|
||||
public override int PostfixMethodPriority => Priority.First;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
public override int FinalizerMethodPriority => Priority.First;
|
||||
}
|
||||
|
||||
public static class FishPatchExtensions
|
||||
{
|
||||
public static void PatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryPatch();
|
||||
}
|
||||
public static void UnpatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryUnpatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPatch> All => _all;
|
||||
public T Get<T>() where T : FishPatch => (T)All[typeof(T)];
|
||||
public void Add(FishPatch patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll() => All.Values.PatchAll();
|
||||
public void UnpatchAll() => All.Values.UnpatchAll();
|
||||
public FishPatch this[Type type] => All[type];
|
||||
|
||||
public FishPatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName); // directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPatch.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(IHasFishPatch[] patches, Type type)
|
||||
{
|
||||
var dupeClasses = patches
|
||||
.Where(patchClass
|
||||
=> patchClass.GetType() != _type
|
||||
&& patchClass.Patches.All.ContainsKey(type));
|
||||
|
||||
if (!dupeClasses.Any())
|
||||
return;
|
||||
|
||||
foreach (var patchClass in dupeClasses)
|
||||
{
|
||||
patchClass.Patches[type].Enabled = false;
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {patchClass.GetType().FullName}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPatch> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private ConcurrentDictionary<Type, FishPatch> _all = new();
|
||||
private Type _type;
|
||||
}
|
||||
|
||||
public interface IHasFishPatch
|
||||
{
|
||||
public FishPatchHolder Patches { get; }
|
||||
public bool RequiresLoadedGameForPatching { get; }
|
||||
}
|
||||
|
||||
public interface IHasDescription
|
||||
{
|
||||
public string? Description { get; }
|
||||
}
|
||||
|
||||
public abstract class ClassWithFishPatches : SingletonFactory<ClassWithFishPatches>, IHasFishPatch
|
||||
{
|
||||
public virtual FishPatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class FishSettings : ModSettings
|
||||
{
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
Scribe_Values.Look(ref _threadingEnabled, "ThreadingEnabled", false);
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
patchClass.Patches.Scribe();
|
||||
}
|
||||
|
||||
public static void DoSettingsWindowContents(Rect inRect)
|
||||
{
|
||||
Widgets.BeginScrollView(inRect, ref _scrollPosition, _scrollRect);
|
||||
var ls = new Listing_Standard();
|
||||
ls.Begin(_scrollRect);
|
||||
|
||||
var checkModEnabled = ModEnabled;
|
||||
ls.CheckboxLabeled("Toggle all patches", ref checkModEnabled, "This enables or disables all patches of the mod with one click. For testing purposes");
|
||||
if (checkModEnabled != ModEnabled)
|
||||
{
|
||||
ModEnabled = checkModEnabled;
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
foreach (var patch in patchClass.Patches)
|
||||
patch.Enabled = checkModEnabled;
|
||||
|
||||
if (checkModEnabled && (!patchClass.RequiresLoadedGameForPatching || Current.ProgramState == ProgramState.Playing))
|
||||
patchClass.Patches.PatchAll();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
try
|
||||
{
|
||||
ls.Label(patchClass.GetType().Name,
|
||||
tooltip: patchClass is IHasDescription classWithDescription
|
||||
? classWithDescription.Description
|
||||
: null);
|
||||
|
||||
ls.GapLine(2f);
|
||||
foreach (var patch in patchClass.Patches)
|
||||
{
|
||||
if (ShouldSkipForScrollView(inRect.height, Text.LineHeight, ls.curY, _scrollPosition.y))
|
||||
{
|
||||
ls.curY += Text.LineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
var check = patch.Enabled;
|
||||
if (patch.Description == null)
|
||||
Widgets.DrawHighlightIfMouseover(_scrollRect with { y = ls.curY, height = Text.LineHeight });
|
||||
var label = patch.Name ?? patch.GetType().Name;
|
||||
if (label.EndsWith("_Patch"))
|
||||
label = label.Remove(label.Length - 6);
|
||||
ls.CheckboxLabeled(label, ref check, patch.Description);
|
||||
if (check != patch.Enabled)
|
||||
patch.Enabled = check;
|
||||
}
|
||||
ls.Gap();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
//ls.Gap();
|
||||
//var threadingEnabled = ThreadingEnabled;
|
||||
//ls.CheckboxLabeled("Allow threading", ref threadingEnabled, "Currently affects the ListerHaulables tick, ingredient lookups in WorkGiver_DoBill and refreshing of the shared altar thoughtworker cache. " +
|
||||
// "Experimental and disabled by default.");
|
||||
//ThreadingEnabled = threadingEnabled;
|
||||
|
||||
ls.Gap();
|
||||
if (ls.ButtonText("Clear Cache"))
|
||||
Cache.Utility.Clear();
|
||||
|
||||
ls.End();
|
||||
|
||||
Widgets.EndScrollView();
|
||||
_scrollRect = _scrollRect with { height = ls.curY + 50f, width = inRect.width - GUI.skin.verticalScrollbar.fixedWidth - 5f };
|
||||
}
|
||||
|
||||
public static bool ShouldSkipForScrollView(float scrollViewSize, float entrySize, float entryPosition, float scrollPosition)
|
||||
=> entryPosition + entrySize < scrollPosition || entryPosition > scrollPosition + scrollViewSize;
|
||||
|
||||
public static bool ModEnabled { get => _modEnabled; set => _modEnabled = value; }
|
||||
private static bool _modEnabled = true;
|
||||
public static bool ThreadingEnabled
|
||||
{
|
||||
get => false; //_threadingEnabled;
|
||||
set
|
||||
{
|
||||
if (value == _threadingEnabled)
|
||||
return;
|
||||
|
||||
ThreadingPatches.ForEach(p => p.Enabled = value);
|
||||
_threadingEnabled = value;
|
||||
}
|
||||
}
|
||||
private static bool _threadingEnabled = false;
|
||||
public static List<FishPatch> ThreadingPatches { get; } = new();
|
||||
private static Rect _scrollRect = new(0f, 0f, 500f, 9001f);
|
||||
private static Vector2 _scrollPosition;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2022 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;
|
||||
|
||||
namespace PerformanceFish.FunctionPointers;
|
||||
|
||||
public static class IndexGetter<T> where T : notnull
|
||||
{
|
||||
public static readonly unsafe delegate*<T, int> Default = IndexGetter.GetDefaultIndexFunctionPointer<T>();
|
||||
}
|
||||
|
||||
public static class IndexGetter
|
||||
{
|
||||
public static class Methods
|
||||
{
|
||||
public static int HediffSet(HediffSet set) => set.pawn.thingIDNumber;
|
||||
public static int HediffDef(HediffDef def) => def.index;
|
||||
public static int Bill(Bill bill) => bill.loadID;
|
||||
public static int Pawn(Pawn pawn) => pawn.thingIDNumber;
|
||||
public static int Thing(Thing thing) => thing.thingIDNumber;
|
||||
public static int CompAssignableToPawn_Bed(CompAssignableToPawn_Bed comp) => comp.parent.thingIDNumber;
|
||||
public static int Map(Map map) => map.uniqueID;
|
||||
public static int DesignationManager(DesignationManager manager) => manager.map.uniqueID;
|
||||
public static int DesignationDef(DesignationDef def) => def.index;
|
||||
public static int Room(Room room) => room.ID;
|
||||
public static int Ideo(Ideo ideo) => ideo.id;
|
||||
public static int RecipeDef(RecipeDef def) => def.index;
|
||||
}
|
||||
|
||||
internal static Cache.IndexGetter<T> GetDefaultIndexGetter<T>() where T : notnull
|
||||
=> _indexFuncs.TryGetValue(typeof(T), out var value)
|
||||
? (Cache.IndexGetter<T>)value
|
||||
: Fallback<T>();
|
||||
|
||||
internal static unsafe delegate*<T, int> GetDefaultIndexFunctionPointer<T>() where T : notnull
|
||||
=> (delegate*<T, int>)(GetGetterMethods().FirstOrDefault(m => m.GetParameters()[0].ParameterType == typeof(T))
|
||||
?? Fallback<T>().Method).MethodHandle.GetFunctionPointer();
|
||||
|
||||
private static IEnumerable<MethodInfo> GetGetterMethods()
|
||||
=> typeof(Methods)
|
||||
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
|
||||
.Where(m
|
||||
=> m.ReturnType == typeof(int)
|
||||
&& m.GetParameters().Length == 1);
|
||||
|
||||
private static Cache.IndexGetter<T> Fallback<T>() where T : notnull
|
||||
{
|
||||
var isDef = typeof(Def).IsAssignableFrom(typeof(T));
|
||||
var isThing = false; //typeof(Thing).IsAssignableFrom(typeof(T)) && !typeof(Mote).IsAssignableFrom(typeof(T)); // generally always safe in vanilla, but logging mods won't hurt either
|
||||
|
||||
var message = $"Performance Fish failed to find type {typeof(T).FullDescription()} in indexFuncs dictionary. Using fallback instead";
|
||||
|
||||
if (isDef)
|
||||
Log.Error($"{message} to prevent further issues, but this likely won't work correctly.");
|
||||
else if (!isThing)
|
||||
Log.Warning($"{message}.");
|
||||
|
||||
var type = typeof(T).BaseType;
|
||||
Delegate func;
|
||||
while (!_indexFuncs.TryGetValue(type, out func))
|
||||
type = type.BaseType ?? throw new Exception($"Performance Fish failed to find a fallback method to handle type {typeof(T).FullDescription()} from mod {typeof(T).Assembly.GetName().Name}");
|
||||
|
||||
if (!isThing)
|
||||
Log.Message($"Found fallback of type {type.FullDescription()}.{(!isDef ? " This likely means we can safely continue from here." : "")}");
|
||||
|
||||
return (Cache.IndexGetter<T>)func;
|
||||
}
|
||||
|
||||
private static Dictionary<Type, Delegate> _indexFuncs
|
||||
= GetGetterMethods()
|
||||
.ToDictionary(
|
||||
m => m.GetParameters()[0].ParameterType,
|
||||
m => m.CreateDelegate(typeof(Cache.IndexGetter<>).MakeGenericType(m.GetParameters()[0].ParameterType)));
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2022 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 System.Reflection.Emit;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class GenLocalDateCaching : ClassWithFishPatches
|
||||
{
|
||||
public class DayTickByThing_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state optimization, but yields accurate results instead of a placeholder value to avoid issues.";
|
||||
public override Delegate? TargetMethodGroup => (Func<Thing, int>)GenLocalDate.DayTick;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes, MethodBase method, ILGenerator generator)
|
||||
//=> Reflection.GetCodeInstructions(Replacement);
|
||||
{
|
||||
var labelToStartOfMethod = generator.DefineLabel();
|
||||
codes.First().labels.Add(labelToStartOfMethod);
|
||||
|
||||
yield return FishTranspiler.Argument(method, "thing");
|
||||
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
|
||||
yield return FishTranspiler.Constant(0);
|
||||
yield return FishTranspiler.IfLessThan(labelToStartOfMethod);
|
||||
|
||||
yield return FishTranspiler.Field(typeof(Current), nameof(Current.gameInt));
|
||||
yield return FishTranspiler.Field(typeof(Game), nameof(Game.maps));
|
||||
yield return FishTranspiler.Argument(method, "thing");
|
||||
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
|
||||
yield return FishTranspiler.Call(typeof(List<Map>), "get_Item");
|
||||
yield return FishTranspiler.Call<Func<Map, int>>(GenLocalDate.DayTick);
|
||||
yield return FishTranspiler.Return;
|
||||
|
||||
foreach (var code in codes)
|
||||
yield return code;
|
||||
|
||||
}
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//public static int Replacement(Thing thing)
|
||||
// => thing.mapIndexOrState < 0 ? GenDate.DayTick(GenTicks.TicksAbs, GenLocalDate.LongitudeForDate(thing))
|
||||
// : GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//public static bool Prefix(Thing thing, ref int __result)
|
||||
//{
|
||||
// if (thing.mapIndexOrState < 0)
|
||||
// return true;
|
||||
|
||||
// __result = GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
|
||||
public class DayTickByMap_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state optimization, but yields accurate results instead of a placeholder value to avoid issues.";
|
||||
public override Delegate? TargetMethodGroup => (Func<Map, int>)GenLocalDate.DayTick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Map map, ref int __result, out bool __state)
|
||||
{
|
||||
if (map != SavedMap)
|
||||
{
|
||||
if (SavedMap is null && Find.Maps?.Count > 0 && Find.Maps[0] == map)
|
||||
SavedMap = map;
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
if (SavedTick != GenTicks.TicksGame)
|
||||
return __state = true;
|
||||
|
||||
__result = DayTick;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Map map, int __result, bool __state)
|
||||
{
|
||||
if (!__state || SavedMap != map)
|
||||
return;
|
||||
|
||||
SavedTick = GenTicks.TicksGame;
|
||||
DayTick = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map? SavedMap;
|
||||
public static int SavedTick;
|
||||
public static int DayTick;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 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 static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class HediffComp_PsychicHarmonizer_Bugfix : ClassWithFishPatches
|
||||
{
|
||||
public class CompPostTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Fixes a bug in the psychic harmonizer tick method that was causing it to recalculate results on 149 out of 150 ticks instead of once every 150 ticks. Large impact.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffComp_PsychicHarmonizer), nameof(HediffComp_PsychicHarmonizer.CompPostTick));
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
var pawn_int_IsHashIntervalTick = FishTranspiler.Call<Func<Thing, int, bool>>(Gen.IsHashIntervalTick);
|
||||
var success = false;
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (i + 2 < codes.Count && codes[i].opcode == Ldc_I4 && codes[i + 1] == pawn_int_IsHashIntervalTick && codes[i + 2].opcode == Brfalse_S)
|
||||
{
|
||||
//codes[i].operand = 128;
|
||||
yield return codes[i];
|
||||
yield return codes[i + 1];
|
||||
codes[i + 2].opcode = Brtrue_S;
|
||||
yield return codes[i + 2];
|
||||
success = true;
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
Log.Warning("Performance Fish failed to insert its Psychic Harmonizer Tick bugfix. This is probably harmless and likely just means someone else fixed it already");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2022 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.Collections;
|
||||
using HediffCache = PerformanceFish.Cache.ByIntRefreshable<Verse.HediffSet, Verse.HediffDef, PerformanceFish.HediffSetCaching.HediffCacheValue>;
|
||||
using VisibleHediffCache = PerformanceFish.Cache.ByIntRefreshable<Verse.HediffSet, Verse.HediffDef, PerformanceFish.HediffSetCaching.VisibleHediffCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class HediffSetCaching : ClassWithFishPatches
|
||||
{
|
||||
public class HasHediff_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the HediffSet.HasHediff method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs.";
|
||||
public override Expression<Action> TargetMethod => () => default(HediffSet)!.HasHediff(null, default);
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.GetCodeInstructions(Replacement);
|
||||
public static bool Replacement(HediffSet __instance, HediffDef def, bool mustBeVisible) => __instance.GetFirstHediffOfDef(def, mustBeVisible) != null;
|
||||
}
|
||||
|
||||
public class GetFirstHediffOfDef_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the HediffSet.GetFirstHediffOfDef method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs.";
|
||||
public override Expression<Action> TargetMethod => () => default(HediffSet)!.GetFirstHediffOfDef(null, default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(HediffSet __instance, HediffDef def, bool mustBeVisible, ref Hediff? __result, out bool __state)
|
||||
{
|
||||
if (__instance.hediffs.Count < 5 || def is null || def.GetType() != typeof(HediffDef)) // different types have different indexing. Can't use those
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new HediffCache() { First = __instance.pawn.thingIDNumber, Second = def.index };
|
||||
ref var cache = ref mustBeVisible ? ref Unsafe.As<VisibleHediffCacheValue, HediffCacheValue>(ref VisibleHediffCache.Get.TryGetReferenceUnsafe(ref Unsafe.As<HediffCache, VisibleHediffCache>(ref key)))
|
||||
: ref HediffCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Hediff;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(HediffSet __instance, HediffDef def, bool mustBeVisible, Hediff? __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
if (mustBeVisible)
|
||||
VisibleHediffCache.Get[new(__instance, def)] = new(__instance, __result);
|
||||
else
|
||||
HediffCache.Get[new(__instance, def)] = new(__instance, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffSet_DirtyCache_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Patched to trigger Psylink cache clearing for the psychic entropy optimization.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache));
|
||||
public static void Postfix(HediffSet __instance)
|
||||
{
|
||||
if (__instance.pawn.psychicEntropy is { } entropy)
|
||||
entropy.psylinkCachedForTick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public struct HediffCacheValue : Cache.IIsRefreshable<HediffCache, HediffCacheValue>
|
||||
{
|
||||
private int _nextRefreshTick;
|
||||
private int _version;
|
||||
private HediffSet _set;
|
||||
public Hediff? Hediff;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public HediffCacheValue(HediffSet set, Hediff? hediff)
|
||||
{
|
||||
_set = set;
|
||||
Hediff = hediff;
|
||||
_version = set.hediffs._version;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.ticksGameInt || _version != _set.hediffs._version;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public HediffCacheValue SetNewValue(HediffCache key) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public struct VisibleHediffCacheValue : Cache.IIsRefreshable<VisibleHediffCache, VisibleHediffCacheValue>
|
||||
{
|
||||
public HediffCacheValue Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public VisibleHediffCacheValue(HediffSet set, Hediff? hediff) => Value = new(set, hediff);
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Value.ShouldRefreshNow;
|
||||
set => Value.ShouldRefreshNow = value;
|
||||
}
|
||||
|
||||
public VisibleHediffCacheValue SetNewValue(VisibleHediffCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2022 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.Cache;
|
||||
using StyleDominanceCache = PerformanceFish.Cache.ByIntRefreshable<Verse.Thing, RimWorld.Ideo, PerformanceFish.IdeoUtilityCaching.StyleDominanceCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class IdeoUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GetStyleDominance_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Caches IdeoUtility.GetStyleDominance to only update about once every 600 ticks or so. Only active when not using Performance Optimizer, as that mod includes a similar version too.";
|
||||
public override bool Enabled => base.Enabled && !ModsConfig.IsActive("Taranchuk.PerformanceOptimizer");
|
||||
public override Delegate? TargetMethodGroup => IdeoUtility.GetStyleDominance;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Thing t, Ideo ideo, ref float __result, out bool __state)
|
||||
{
|
||||
if (t is null || ideo is null || t.thingIDNumber < 0)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new StyleDominanceCache(t.thingIDNumber, ideo.id);
|
||||
ref var cache = ref StyleDominanceCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Value;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Thing t, Ideo ideo, bool __state, float __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
StyleDominanceCache.Get[new(t, ideo)] = new(__result);
|
||||
}
|
||||
}
|
||||
|
||||
public struct StyleDominanceCacheValue : IIsRefreshable<StyleDominanceCache, StyleDominanceCacheValue>
|
||||
{
|
||||
public float Value;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public StyleDominanceCacheValue(float value)
|
||||
{
|
||||
Value = value;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 512 + Math.Abs(Rand.Int % 256); // Pawn_StyleObserverTracker.StyleObservableTick runs every 900 ticks
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 512 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
|
||||
StyleDominanceCacheValue IIsRefreshable<StyleDominanceCache, StyleDominanceCacheValue>.SetNewValue(StyleDominanceCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2022 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.Threading.Tasks;
|
||||
using Verse.AI;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class GenClosestPatches : ClassWithFishPatches
|
||||
{*/
|
||||
#if disabledForNow
|
||||
public class ClosestThing_Global_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Threading for job scanning. Disabled and unstable.";
|
||||
public override bool DefaultState => false;
|
||||
public static bool ThreadingEnabled { get; set; }
|
||||
public override Expression<Action> TargetMethod => () => GenClosest.ClosestThing_Global(default, null, default, null, null);
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ClosestThing_Global_Replacement);
|
||||
private struct Variables
|
||||
{
|
||||
public IntVec3 center;
|
||||
public float maxDistanceSquared;
|
||||
public Func<Thing, float>? priorityGetter;
|
||||
public float closestDistSquared;
|
||||
public Predicate<Thing>? validator;
|
||||
}
|
||||
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable searchSet, float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? priorityGetter = null)
|
||||
{
|
||||
if (searchSet == null)
|
||||
return null;
|
||||
var closestDistSquared = 2.14748365E+09f;
|
||||
Thing? chosen = null;
|
||||
var bestPrio = float.MinValue;
|
||||
//float maxDistanceSquared = maxDistance * maxDistance;
|
||||
Variables variables = new() { center = center, maxDistanceSquared = maxDistance * maxDistance, validator = validator, priorityGetter = priorityGetter, closestDistSquared = closestDistSquared };
|
||||
|
||||
var predicate = GenClosest_Predicate.GetValidator(validator?.Target)?.Target ?? validator?.Target;
|
||||
if (ThreadingEnabled && validator != null && JobGiver_Work_Predicate.GetScanner(predicate) is WorkGiver_DoBill /*or WorkGiver_ConstructDeliverResources or WorkGiver_ConstructFinishFrames*/)
|
||||
{
|
||||
if (searchSet is IList<Thing> thingList)
|
||||
{
|
||||
Log.Message($"Running threading code for {validator.Target.GetType()}");
|
||||
Exception? exception = null;
|
||||
try
|
||||
{
|
||||
Parallel.For<(IList<Thing> list, Variables vars)>(0, thingList.Count,
|
||||
() => (thingList, variables),
|
||||
(i, loop, listAndVars) => (listAndVars.list, ProcessThreaded(listAndVars.list[i], listAndVars.vars)),
|
||||
_ => { });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
Log.Error($"Caught error while threading:\n{ex}");
|
||||
}
|
||||
if (exception is null)
|
||||
Log.Message("Finished threading code without errors");
|
||||
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
switch (searchSet)
|
||||
{
|
||||
case IList<Thing> thingList:
|
||||
{
|
||||
for (var i = 0; i < thingList.Count; i++)
|
||||
Process(thingList[i], variables);
|
||||
break;
|
||||
}
|
||||
case IList<Pawn> pawnList:
|
||||
{
|
||||
for (var j = 0; j < pawnList.Count; j++)
|
||||
Process(pawnList[j], variables);
|
||||
break;
|
||||
}
|
||||
case IList<Building> buildingList:
|
||||
{
|
||||
for (var k = 0; k < buildingList.Count; k++)
|
||||
Process(buildingList[k], variables);
|
||||
break;
|
||||
}
|
||||
case IList<IAttackTarget> attackTargetList:
|
||||
{
|
||||
for (var l = 0; l < attackTargetList.Count; l++)
|
||||
Process((Thing)attackTargetList[l], variables);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
foreach (Thing item in searchSet)
|
||||
Process(item, variables);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chosen;
|
||||
void Process<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
float num = (variables.center - t.Position).LengthHorizontalSquared;
|
||||
if (!(num > variables.maxDistanceSquared)
|
||||
&& (variables.priorityGetter != null || num < closestDistSquared)
|
||||
&& (variables.validator == null || variables.validator(t)))
|
||||
{
|
||||
var num2 = 0f;
|
||||
if (variables.priorityGetter != null)
|
||||
{
|
||||
num2 = variables.priorityGetter(t);
|
||||
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
|
||||
return;
|
||||
}
|
||||
chosen = t;
|
||||
closestDistSquared = variables.closestDistSquared = num;
|
||||
bestPrio = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
Variables ProcessThreaded<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
float num = (variables.center - t.Position).LengthHorizontalSquared;
|
||||
if (!(num > variables.maxDistanceSquared)
|
||||
&& (variables.priorityGetter != null || (num < variables.closestDistSquared && num < (variables.closestDistSquared = closestDistSquared)))
|
||||
&& (variables.validator == null || variables.validator(t)))
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var num2 = 0f;
|
||||
if (variables.priorityGetter != null)
|
||||
{
|
||||
num2 = variables.priorityGetter(t);
|
||||
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
|
||||
return variables;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (num >= closestDistSquared)
|
||||
return variables;
|
||||
}
|
||||
chosen = t;
|
||||
closestDistSquared = variables.closestDistSquared = num;
|
||||
bestPrio = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
private static Type JobGiverWorkPredicateType { get; } = AccessTools.FirstInner(typeof(JobGiver_Work),
|
||||
t => AccessTools.Field(t, "scanner") != null && AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage")) != null);
|
||||
private static JobGiver_Work_PredicateAccess JobGiver_Work_Predicate { get; }
|
||||
= (JobGiver_Work_PredicateAccess)Activator.CreateInstance(typeof(JobGiver_Work_PredicateAccessImplementation<>).MakeGenericType(JobGiverWorkPredicateType));
|
||||
private static Type GenClosestPredicateType { get; } = AccessTools.FirstInner(typeof(GenClosest),
|
||||
t => AccessTools.Field(t, "validator") != null && AccessTools.FirstMethod(t, m => m.Name.Contains("ClosestThingReachable")) != null);
|
||||
private static GenClosest_PredicateAccess GenClosest_Predicate { get; }
|
||||
= (GenClosest_PredicateAccess)Activator.CreateInstance(typeof(GenClosest_PredicateAccessImplementation<>).MakeGenericType(GenClosestPredicateType));
|
||||
private abstract class JobGiver_Work_PredicateAccess
|
||||
{
|
||||
public abstract WorkGiver_Scanner? GetScanner(object? obj);
|
||||
}
|
||||
private class JobGiver_Work_PredicateAccessImplementation<T> : JobGiver_Work_PredicateAccess
|
||||
{
|
||||
public override WorkGiver_Scanner? GetScanner(object? obj) => obj is T t ? Scanner(t) : null;
|
||||
public static AccessTools.FieldRef<T, WorkGiver_Scanner> Scanner { get; } = AccessTools.FieldRefAccess<T, WorkGiver_Scanner>("scanner");
|
||||
}
|
||||
private abstract class GenClosest_PredicateAccess
|
||||
{
|
||||
public abstract Predicate<Thing>? GetValidator(object? obj);
|
||||
}
|
||||
private class GenClosest_PredicateAccessImplementation<T> : GenClosest_PredicateAccess
|
||||
{
|
||||
public override Predicate<Thing>? GetValidator(object? obj) => obj is T t ? Validator(t) : null;
|
||||
public static AccessTools.FieldRef<T, Predicate<Thing>> Validator { get; } = AccessTools.FieldRefAccess<T, Predicate<Thing>>("validator");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if threadingEnabled
|
||||
public class CanReach_Patch : FishPatch
|
||||
{
|
||||
protected CanReach_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Expression<Action> TargetMethod => () => default(Reachability)!.CanReach(default, null, default, default);
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Enter(Lock);
|
||||
}
|
||||
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Exit(Lock);
|
||||
return __exception;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
#endif
|
||||
//}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class JobGiver_HaulOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PotentialWorkThingsGlobal_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Haul), nameof(JobGiver_Haul.TryGiveJob));
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
var pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling = FishTranspiler.Call(() => default(ListerHaulables)!.ThingsPotentiallyNeedingHauling());
|
||||
|
||||
try
|
||||
{
|
||||
return codes.InsertAfter(
|
||||
c => c == pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling,
|
||||
new CodeInstruction[]
|
||||
{
|
||||
FishTranspiler.Argument(method, "pawn"),
|
||||
FishTranspiler.Call(SortedThingsPotentiallyNeedingHauling)
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
//Log.Error("Performance Fish failed to apply its JobGiver_Haul transpiler. This particular optimization will be doing nothing now");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn)
|
||||
{
|
||||
_comparer.rootCell = pawn.Position;
|
||||
things.Sort(_comparer);
|
||||
return things;
|
||||
}
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// Copyright (c) 2022 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.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
using static PerformanceFish.TranspilerHelpers;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
{
|
||||
public class TryIssueJobPackage_InnerDelegate_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then threads the IsForbidden loop, sorts it, caches the result and skips any following duplicate checks";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.FindIncludingInnerTypes(typeof(JobGiver_Work),
|
||||
t => AccessTools.Field(t, "scanner") != null ? AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool)) : null);
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var shouldRemove = true;
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
if (shouldRemove)
|
||||
{
|
||||
if (code.Branches(out _))
|
||||
shouldRemove = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.opcode == Ret)
|
||||
shouldRemove = true;
|
||||
yield return code;
|
||||
}
|
||||
}
|
||||
//Replaces
|
||||
//(Thing t) => !t.IsForbidden(pawn) && scanner.HasJobOnThing(pawn, t);
|
||||
//with
|
||||
//(Thing t) => scanner.HasJobOnThing(pawn, t);
|
||||
}
|
||||
}
|
||||
|
||||
public class TryIssueJobPackage_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then threads the IsForbidden loop, sorts it, caches the result and skips any following duplicate checks";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Work), nameof(JobGiver_Work.TryIssueJobPackage));
|
||||
public static void Prefix(Pawn pawn)
|
||||
{
|
||||
PrefilteredPotentialWorkThingsGlobal.Clear();
|
||||
FishCache.ClearAll();
|
||||
CurrentPawn = pawn;
|
||||
}
|
||||
public static void Postfix()
|
||||
{
|
||||
PrefilteredPotentialWorkThingsGlobal.Clear();
|
||||
FishCache.ClearAll();
|
||||
CurrentPawn = null;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase original)
|
||||
{
|
||||
var pawn = GetLdarg(original, "pawn");
|
||||
|
||||
var scanner_pawn_PotentialWorkThingsGlobal = GetCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal));
|
||||
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType = GetCall(() => TryGetPrefilteredPotentialWorkThingsAnyType(null, null));
|
||||
|
||||
var scanner_PotentialWorkThingRequest = GetGetPropertyCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingRequest));
|
||||
var scanner_pawn_GetCachedThingRequest = GetCall(() => GetCachedThingRequest(null, null));
|
||||
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
if (code == scanner_pawn_PotentialWorkThingsGlobal)
|
||||
{
|
||||
yield return code;
|
||||
yield return pawn;
|
||||
yield return potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType;
|
||||
}
|
||||
else if (code == scanner_PotentialWorkThingRequest)
|
||||
{
|
||||
yield return pawn;
|
||||
yield return scanner_pawn_GetCachedThingRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ThingRequest GetCachedThingRequest(WorkGiver_Scanner scanner, Pawn pawn)
|
||||
{
|
||||
var originalRequest = scanner.PotentialWorkThingRequest;
|
||||
if (originalRequest is { singleDef: null, group: ThingRequestGroup.Undefined })
|
||||
return originalRequest;
|
||||
|
||||
var listerThings = pawn.Map.listerThings.ThingsMatching(originalRequest);
|
||||
var collection = listerThings.Count > 100 ? (scanner.PotentialWorkThingsGlobal(pawn) as ICollection<Thing>) : null;
|
||||
|
||||
var processedThings = TryGetPrefilteredPotentialWorkThingsAnyType(collection is null || listerThings.Count < collection.Count * 4 ? listerThings : collection, pawn);
|
||||
var fishCache = originalRequest.singleDef != null ? FishCache.ForDef(originalRequest.singleDef) : FishCache.ForGroup(originalRequest.group);
|
||||
fishCache.FilteredThings = processedThings as List<Thing> ?? new(processedThings);
|
||||
return new ThingRequest() { group = originalRequest.group, singleDef = fishCache };
|
||||
}
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsAnyType(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
=> originalWorkThings switch
|
||||
{
|
||||
List<Thing> => Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
|
||||
List<Pawn> => Generic<Pawn>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
|
||||
_ => GetPrefilteredPotentialWorkThingsUncached(originalWorkThings, pawn)
|
||||
};
|
||||
public static IEnumerable<Thing> GetPrefilteredPotentialWorkThingsUncached(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
=> (originalWorkThings is not ICollection<Thing> collection || collection.Count < 2048)
|
||||
? CheckForbidden(originalWorkThings, pawn)
|
||||
: Generic<Thing>.Threaded_CheckForbiddenAndAddToAnyCollection(collection, pawn);
|
||||
|
||||
public static IEnumerable<Thing> CheckForbidden(IEnumerable<Thing> things, Pawn pawn)
|
||||
{
|
||||
if (things is null)
|
||||
yield break;
|
||||
|
||||
foreach (var thing in things)
|
||||
{
|
||||
if (!thing.IsForbidden(pawn))
|
||||
yield return thing;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Generic<T> where T : Thing
|
||||
{
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
if (!originalWorkThings.Any())
|
||||
return originalWorkThings;
|
||||
|
||||
if (!PrefilteredPotentialWorkThingsGlobal.TryGetValue(originalWorkThings, out var cache))
|
||||
cache = RecalculatePrefilteredPotentialWorkThings(originalWorkThings, pawn);
|
||||
|
||||
return cache;
|
||||
}
|
||||
public static T[] RecalculatePrefilteredPotentialWorkThings(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
var inputList = originalWorkThings as List<T>;
|
||||
if (inputList.Count < 512)
|
||||
Sequential_CheckForbiddenAndAddToList(inputList, pawn);
|
||||
else
|
||||
Threaded_CheckForbiddenAndAddToList(inputList, pawn);
|
||||
|
||||
var cache = _tempThingList.ToArray();
|
||||
if (cache.Length < 1024)
|
||||
{
|
||||
var pawnPosition = pawn.Position;
|
||||
Array.Sort(cache, (a, b) => (pawnPosition - a.Position).LengthHorizontalSquared.CompareTo((pawnPosition - b.Position).LengthHorizontalSquared));
|
||||
}
|
||||
|
||||
PrefilteredPotentialWorkThingsGlobal[originalWorkThings] = cache;
|
||||
_tempThingList.Clear();
|
||||
|
||||
return cache;
|
||||
}
|
||||
private static void Sequential_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
var tempThingList = _tempThingList;
|
||||
var count = inputList.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (!inputList[i].IsForbidden(pawn))
|
||||
tempThingList.Add(inputList[i]);
|
||||
}
|
||||
}
|
||||
private static void Threaded_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
#if DEBUG
|
||||
DebugLog.Message("Running threading code");
|
||||
var stopwatch = new System.Diagnostics.Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
var partitioner = Partitioner.Create(0, inputList.Count);
|
||||
|
||||
Parallel.ForEach<Tuple<int, int>, (List<T> localList, List<T> sourceList, Pawn pawn)>(partitioner,
|
||||
() => new(new(), inputList, pawn),
|
||||
(range, state, vars) =>
|
||||
{
|
||||
for (int i = range.Item1; i < range.Item2; i++)
|
||||
{
|
||||
var item = vars.sourceList[i];
|
||||
if (!item.IsForbidden(vars.pawn))
|
||||
vars.localList.Add(item);
|
||||
}
|
||||
return vars;
|
||||
},
|
||||
vars =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_tempThingList.AddRange(vars.localList);
|
||||
}
|
||||
});
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
|
||||
#endif
|
||||
}
|
||||
public static List<T> Threaded_CheckForbiddenAndAddToAnyCollection(ICollection<T> inputCollection, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
#if DEBUG
|
||||
DebugLog.Message("Running threading code");
|
||||
var stopwatch = new System.Diagnostics.Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
|
||||
Parallel.ForEach<T, (List<T> localList, Pawn pawn)>(inputCollection,
|
||||
() => new(new(), pawn),
|
||||
(thing, state, vars) =>
|
||||
{
|
||||
if (!thing.IsForbidden(vars.pawn))
|
||||
vars.localList.Add(thing);
|
||||
return vars;
|
||||
},
|
||||
vars =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_tempThingList.AddRange(vars.localList);
|
||||
}
|
||||
});
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
|
||||
#endif
|
||||
return _tempThingList;
|
||||
}
|
||||
|
||||
private static object _lock { get; } = new();
|
||||
private static List<T> _tempThingList { get; } = new();
|
||||
}
|
||||
|
||||
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; } = new();
|
||||
}
|
||||
|
||||
public class ThingRequest_IsUndefined_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
|
||||
public class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
|
||||
public static bool Prefix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
return false;
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> ((FishCache)__instance.singleDef).IsSingleDef
|
||||
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
|
||||
}
|
||||
|
||||
public class ListerThings_ThingsMatching_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var cachedThings = ((FishCache)req.singleDef).FilteredThings ?? ListerThings.EmptyList;
|
||||
if (__instance.use == ListerThingsUse.Region && cachedThings.Count > 20)
|
||||
{
|
||||
var regionalThings = GetForRegion(__instance, req);
|
||||
return regionalThings.Count < cachedThings.Count ? regionalThings : cachedThings;
|
||||
}
|
||||
else
|
||||
{
|
||||
return cachedThings;
|
||||
}
|
||||
}
|
||||
public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req)
|
||||
=> new(TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
|
||||
(((FishCache)req.singleDef) is var cache && cache.IsSingleDef
|
||||
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null
|
||||
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn));
|
||||
}
|
||||
|
||||
public class FishCache : ThingDef
|
||||
{
|
||||
public bool IsSingleDef => Def != null;
|
||||
public ThingDef Def { get; private set; }
|
||||
public List<Thing> FilteredThings { get; set; }
|
||||
public static FishCache ForDef(ThingDef def)
|
||||
{
|
||||
if (_dictOfDefs.TryGetValue(def, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfDefs[def] = value = new(def.defName) { Def = def };
|
||||
return value;
|
||||
}
|
||||
public static FishCache ForGroup(ThingRequestGroup group)
|
||||
{
|
||||
if (_dictOfGroups.TryGetValue(group, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfGroups[group] = value = new(group.ToString());
|
||||
return value;
|
||||
}
|
||||
public static void ClearAll()
|
||||
{
|
||||
_dictOfGroups.Clear();
|
||||
_dictOfDefs.Clear();
|
||||
}
|
||||
private FishCache(string name) => defName = name;
|
||||
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new();
|
||||
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
|
||||
}
|
||||
|
||||
private static Pawn CurrentPawn { get; set; }
|
||||
}*/
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using Verse.AI;
|
||||
using IngredientCacheList = PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs, bool Found)>;
|
||||
using RecipeIngredientCache = PerformanceFish.Cache.ByIndex<RimWorld.Bill, PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
{
|
||||
public static List<Type> BlackListedModExtensions => _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
|
||||
private static List<Type>? _blackListedModExtensions;
|
||||
|
||||
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes WorkGiver_DoBill to only check things that get accepted by a bill's filter for its ingredient lookup, instead of looping over everything and then checking filters afterwards. " +
|
||||
"Also caches these filter acceptances. Large performance impact.";
|
||||
|
||||
public override MethodBase TargetMethodInfo
|
||||
=> AccessTools.FirstMethod(DelegateInstanceType, m
|
||||
=> m.GetParameters() is { Length: 1 } parms
|
||||
&& parms[0].ParameterType == typeof(Region)
|
||||
&& m.ReturnType == typeof(bool)
|
||||
&& PatchProcessor.ReadMethodBody(m).Any(pair
|
||||
=> (pair.Key == OpCodes.Call || pair.Key == OpCodes.Callvirt)
|
||||
&& pair.Value is MethodInfo info
|
||||
&& info == AccessTools.Method(typeof(ListerThings), nameof(ListerThings.ThingsMatching))));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase method)
|
||||
=> Reflection.MakeReplacementCall(
|
||||
AccessTools.Method(typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(Replacement), generics: new[] { DelegateInstanceType }));
|
||||
|
||||
public static bool Replacement<T>(T __instance, Region r) where T : class
|
||||
{
|
||||
var instance = _delegateInstance.Get(__instance);
|
||||
var thingValidator = _thingValidatorInstance.Get(instance.ThingValidator.Target);
|
||||
|
||||
List<Thing> list;
|
||||
if (thingValidator is null) // <-- TryFindBestFixedIngredients, used by biosculptors, goes here. Fuck them
|
||||
{
|
||||
list = GetDefaultList(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bill = thingValidator.Bill;
|
||||
|
||||
if (HasNoFilters(bill) // apparently applies to a rim reborn benches?
|
||||
|| (BlackListedModExtensions.Exists(b => b != null) && IsBlackListed(bill))) // <-- Fuck these too
|
||||
{
|
||||
list = GetDefaultList(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var cache = ref RecipeIngredientCache.Get.GetReference(bill);
|
||||
if (cache.thingDefs is null || cache.ShouldRefreshNow)
|
||||
{
|
||||
cache.thingDefs = UpdateCache(bill, r, cache.thingDefs);
|
||||
cache.ShouldRefreshNow = false;
|
||||
RecipeIngredientCache.Get[bill] = cache;
|
||||
}
|
||||
list = GetList(r.listerThings.listsByDef, cache.thingDefs);
|
||||
}
|
||||
}
|
||||
|
||||
ActualLoop(list, r, instance.Pawn, instance.BaseValidator, instance.BillGiverIsPawn);
|
||||
|
||||
//list.Clear();
|
||||
|
||||
instance.RegionsProcessed++;
|
||||
if (WorkGiver_DoBill.newRelevantThings.Count > 0 && instance.RegionsProcessed > instance.AdjacentRegionsAvailable)
|
||||
{
|
||||
//WorkGiver_DoBill.relevantThings.AddRange(WorkGiver_DoBill.newRelevantThings);
|
||||
InsertAtCorrectPosition(WorkGiver_DoBill.relevantThings, WorkGiver_DoBill.newRelevantThings, instance.Pawn);
|
||||
|
||||
WorkGiver_DoBill.newRelevantThings.Clear();
|
||||
if (instance.FoundAllIngredientsAndChoose(WorkGiver_DoBill.relevantThings))
|
||||
{
|
||||
instance.FoundAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasNoFilters(Bill bill)
|
||||
=> bill.ingredientFilter is null
|
||||
|| bill.recipe.fixedIngredientFilter is null;
|
||||
|
||||
/*private static bool HasNoFilters(Bill bill)
|
||||
{
|
||||
if (bill.ingredientFilter is null
|
||||
|| bill.recipe.fixedIngredientFilter is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var ingredients = bill.recipe.ingredients;
|
||||
for (var i = 0; i < ingredients.Count; i++)
|
||||
{
|
||||
if (ingredients[i].filter is null)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
private static bool IsBlackListed(Bill bill)
|
||||
=> bill is null
|
||||
|| (bill.recipe.modExtensions?.Exists(e
|
||||
=> BlackListedModExtensions.Contains(e.GetType()))
|
||||
?? false);
|
||||
|
||||
private static List<Thing> GetDefaultList(Region r) => r.ListerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
|
||||
|
||||
public static void InsertAtCorrectPosition(List<Thing> list, List<Thing> thingsToInsert, Pawn pawn)
|
||||
{
|
||||
var count = thingsToInsert.Count;
|
||||
var comparer = _insertAtCorrectPositionComparer;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var thing = thingsToInsert[i];
|
||||
comparer.rootCell = WorkGiver_DoBill.GetBillGiverRootCell(thingsToInsert[i], pawn);
|
||||
|
||||
var index = ~list.BinarySearch(thing, comparer);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
list.Insert(index, thing);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new();
|
||||
|
||||
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator, bool billGiverIsPawn)
|
||||
{
|
||||
var count = list.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var thing = list[i];
|
||||
if (!WorkGiver_DoBill.processedThings.Contains(thing)
|
||||
&& ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch, pawn) && !(billGiverIsPawn && thing.def.IsMedicine) && baseValidator(thing))
|
||||
{
|
||||
WorkGiver_DoBill.newRelevantThings.Add(thing);
|
||||
WorkGiver_DoBill.processedThings.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Thing> GetList(Dictionary<ThingDef, List<Thing>> listsByDef, IngredientCacheList cacheList)
|
||||
{
|
||||
var list = _tempListForIngredients;
|
||||
list.Clear();
|
||||
var set = _tempSetForIngredientDefs;
|
||||
set.Clear();
|
||||
|
||||
for (var i = 0; i < cacheList.Count; i++)
|
||||
{
|
||||
var (defs, found) = cacheList[i].Value;
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
var count = defs.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (listsByDef.TryGetValue(defs[j], out var listByDef) && listByDef.Any())
|
||||
{
|
||||
if (i > 0 && !set.Add(defs[j]))
|
||||
continue;
|
||||
|
||||
list.AddRange(listByDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IngredientCacheList UpdateCache(Bill bill, Region r, IngredientCacheList? cacheList)
|
||||
{
|
||||
var recipe = bill.recipe;
|
||||
var ingredients = recipe.ingredients;
|
||||
var listsByDef = r.Map.listerThings.listsByDef;
|
||||
var result = cacheList ?? new(3);
|
||||
|
||||
for (var i = 0; i < ingredients.Count; i++) // generally not larger than 3 in vanilla and anything that tries to be
|
||||
{
|
||||
if (result.TryGetValue(ingredients[i], out var ingredientList))
|
||||
{
|
||||
ingredientList.Defs.Clear();
|
||||
ingredientList.Found = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[ingredients[i]] = ingredientList = new(new(), false);
|
||||
}
|
||||
|
||||
var filter = ingredients[i].filter;
|
||||
var allowedDefCount = filter.allowedDefs.Count;
|
||||
|
||||
if (allowedDefCount < 1)
|
||||
continue;
|
||||
|
||||
if (allowedDefCount == 1)
|
||||
{
|
||||
ingredientList.Defs.Add(filter.allowedDefs.First());
|
||||
continue;
|
||||
}
|
||||
|
||||
var smallestFilter = bill.ingredientFilter;
|
||||
var mediumFilter = recipe.fixedIngredientFilter;
|
||||
var largestFilter = filter;
|
||||
|
||||
OrderFiltersBySize(ref smallestFilter, ref mediumFilter, ref largestFilter);
|
||||
|
||||
var cachedSmallestList = AllowedDefsListCache.GetValue(smallestFilter).defs;
|
||||
if (cachedSmallestList.Count != smallestFilter.allowedDefs.Count)
|
||||
{
|
||||
// Log.Warning($"Cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}. Forcing a refresh now to fix.");
|
||||
ThingFilterPatches.ForceSynchronizeCache(smallestFilter);
|
||||
// Log.Warning($"New cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}.");
|
||||
// didn't trigger in 1.3, but now does. Fuck
|
||||
}
|
||||
var smallestListCount = cachedSmallestList.Count;
|
||||
for (var k = 0; k < smallestListCount; k++)
|
||||
{
|
||||
var def = cachedSmallestList[k];
|
||||
if (listsByDef.TryGetValue(def, out var listByDef) && listByDef.Any()
|
||||
&& mediumFilter.Allows(def)
|
||||
&& largestFilter.Allows(def))
|
||||
{
|
||||
ingredientList.Defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter, ref ThingFilter largestFilter)
|
||||
{
|
||||
if (smallestFilter.allowedDefs.Count > mediumFilter.allowedDefs.Count)
|
||||
(smallestFilter, mediumFilter) = (mediumFilter, smallestFilter);
|
||||
|
||||
if (smallestFilter.allowedDefs.Count > largestFilter.allowedDefs.Count)
|
||||
(smallestFilter, largestFilter) = (largestFilter, smallestFilter);
|
||||
|
||||
if (mediumFilter.allowedDefs.Count > largestFilter.allowedDefs.Count)
|
||||
(mediumFilter, largestFilter) = (largestFilter, mediumFilter);
|
||||
}
|
||||
|
||||
private static readonly List<Thing> _tempListForIngredients = new();
|
||||
private static readonly HashSet<ThingDef> _tempSetForIngredientDefs = new();
|
||||
}
|
||||
|
||||
private static Type DelegateInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>")) != null
|
||||
&& AccessTools.Field(t, "pawn") != null
|
||||
&& AccessTools.Field(t, "baseValidator") != null
|
||||
&& AccessTools.Field(t, "billGiverIsPawn") != null
|
||||
&& AccessTools.Field(t, "regionsProcessed") != null
|
||||
&& AccessTools.Field(t, "adjacentRegionsAvailable") != null
|
||||
&& AccessTools.Field(t, "foundAllIngredientsAndChoose") != null
|
||||
&& AccessTools.Field(t, "foundAll") != null
|
||||
&& AccessTools.Field(t, "thingValidator") != null)
|
||||
?? throw new("Performance Fish failed to find the target type for its WorkGiver_DoBill patch. This means that specific patch won't apply.");
|
||||
|
||||
private static Type ThingValidatorInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestBillIngredients)}>")) != null
|
||||
&& AccessTools.Field(t, "bill") != null
|
||||
//&& AccessTools.Field(t, "chosen") != null
|
||||
//&& AccessTools.Field(t, "billGiver") != null
|
||||
/*&& AccessTools.Field(t, "pawn") != null*/)
|
||||
?? throw new("Performance Fish failed to find the thingValidator type for its WorkGiver_DoBill patch. This likely flat out breaks the entire patch.");
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
private static DelegateInstance _delegateInstance { get; }
|
||||
= (DelegateInstance)Activator.CreateInstance(typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
private static ThingValidatorInstance _thingValidatorInstance { get; }
|
||||
= (ThingValidatorInstance)Activator.CreateInstance(typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
|
||||
|
||||
private abstract class DelegateInstance
|
||||
{
|
||||
public abstract DelegateInstance Get(object obj);
|
||||
|
||||
public abstract Pawn Pawn { get; set; }
|
||||
public abstract Predicate<Thing> BaseValidator { get; set; }
|
||||
public abstract bool BillGiverIsPawn { get; set; }
|
||||
public abstract int RegionsProcessed { get; set; }
|
||||
public abstract int AdjacentRegionsAvailable { get; set; }
|
||||
public abstract Predicate<List<Thing>> FoundAllIngredientsAndChoose { get; set; }
|
||||
public abstract bool FoundAll { get; set; }
|
||||
public abstract Predicate<Thing> ThingValidator { get; set; }
|
||||
}
|
||||
|
||||
private class DelegateInstanceImplementation<T> : DelegateInstance where T : class
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private T _instance;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public override DelegateInstance Get(object obj)
|
||||
{
|
||||
_instance = (T)obj;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override Pawn Pawn { get => _pawn(_instance); set => _pawn(_instance) = value; }
|
||||
public override Predicate<Thing> BaseValidator { get => _baseValidator(_instance); set => _baseValidator(_instance) = value; }
|
||||
public override bool BillGiverIsPawn { get => _billGiverIsPawn(_instance); set => _billGiverIsPawn(_instance) = value; }
|
||||
public override int RegionsProcessed { get => _regionsProcessed(_instance); set => _regionsProcessed(_instance) = value; }
|
||||
public override int AdjacentRegionsAvailable { get => _adjacentRegionsAvailable(_instance); set => _adjacentRegionsAvailable(_instance) = value; }
|
||||
public override Predicate<List<Thing>> FoundAllIngredientsAndChoose { get => _foundAllIngredientsAndChoose(_instance); set => _foundAllIngredientsAndChoose(_instance) = value; }
|
||||
public override bool FoundAll { get => _foundAll(_instance); set => _foundAll(_instance) = value; }
|
||||
public override Predicate<Thing> ThingValidator { get => _thingValidator(_instance); set => _thingValidator(_instance) = value; }
|
||||
|
||||
private static AccessTools.FieldRef<T, Pawn> _pawn = AccessTools.FieldRefAccess<T, Pawn>("pawn");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _baseValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("baseValidator");
|
||||
private static AccessTools.FieldRef<T, bool> _billGiverIsPawn = AccessTools.FieldRefAccess<T, bool>("billGiverIsPawn");
|
||||
private static AccessTools.FieldRef<T, int> _regionsProcessed = AccessTools.FieldRefAccess<T, int>("regionsProcessed");
|
||||
private static AccessTools.FieldRef<T, int> _adjacentRegionsAvailable = AccessTools.FieldRefAccess<T, int>("adjacentRegionsAvailable");
|
||||
private static AccessTools.FieldRef<T, Predicate<List<Thing>>> _foundAllIngredientsAndChoose = AccessTools.FieldRefAccess<T, Predicate<List<Thing>>>("foundAllIngredientsAndChoose");
|
||||
private static AccessTools.FieldRef<T, bool> _foundAll = AccessTools.FieldRefAccess<T, bool>("foundAll");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
|
||||
}
|
||||
|
||||
private abstract class ThingValidatorInstance
|
||||
{
|
||||
public abstract ThingValidatorInstance? Get(object obj);
|
||||
|
||||
public abstract Bill Bill { get; set; }
|
||||
//public abstract Thing BillGiver { get; set; }
|
||||
}
|
||||
|
||||
private class ThingValidatorInstanceImplementation<T> : ThingValidatorInstance where T : class
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private T _instance;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public override ThingValidatorInstance? Get(object obj)
|
||||
{
|
||||
if (obj is T t)
|
||||
{
|
||||
_instance = t;
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override Bill Bill { get => _bill(_instance); set => _bill(_instance) = value; }
|
||||
//public override Thing BillGiver { get => _billGiver(_instance); set => _billGiver(_instance) = value; }
|
||||
|
||||
private static AccessTools.FieldRef<T, Bill> _bill = AccessTools.FieldRefAccess<T, Bill>("bill");
|
||||
//private static AccessTools.FieldRef<T, Thing> _billGiver = AccessTools.FieldRefAccess<T, Thing>("billGiver");
|
||||
}
|
||||
|
||||
public class TryFindBestIngredientsHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsHelper;
|
||||
|
||||
public static void Postfix(Predicate<Thing> thingValidator)
|
||||
{
|
||||
var thingValidatorInstance = _thingValidatorInstance.Get(thingValidator.Target);
|
||||
if (thingValidatorInstance is null)
|
||||
return;
|
||||
|
||||
var cache = RecipeIngredientCache.Get[thingValidatorInstance.Bill].thingDefs;
|
||||
if (cache is null)
|
||||
return;
|
||||
|
||||
var items = cache._items;
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
ref var item = ref items[i];
|
||||
if (item.Value.Found)
|
||||
item = new(item.Key, new(item.Value.Defs, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
|
||||
|
||||
public static void Prefix(ref bool alreadySorted) => alreadySorted = true;
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
var i_variable = FishTranspiler.FirstLocalVariable(method, typeof(int));
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
=> i + 3 < codes.Count
|
||||
&& codes[i] == i_variable.Load()
|
||||
&& codes[i + 1] == FishTranspiler.Constant(1) // i++;
|
||||
&& codes[i + 2] == FishTranspiler.Add
|
||||
&& codes[i + 3] == i_variable.Store(),
|
||||
code
|
||||
=> new[]
|
||||
{
|
||||
FishTranspiler.FirstLocalVariable(method, typeof(IngredientCount))
|
||||
.WithLabels(code.ExtractLabels()),
|
||||
FishTranspiler.FirstArgument(method, typeof(Bill)),
|
||||
FishTranspiler.Call(MarkIngredientCountAsFound),
|
||||
code
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill bill)
|
||||
{
|
||||
if (bill is null)
|
||||
return;
|
||||
|
||||
var cache = RecipeIngredientCache.Get[bill].thingDefs;
|
||||
if (cache is null)
|
||||
return;
|
||||
|
||||
ref var cacheItem = ref cache._items[cache.IndexOfKey(ingredientCount)];
|
||||
|
||||
cacheItem = new(cacheItem.Key, new(cacheItem.Value.Defs, true));
|
||||
}
|
||||
}
|
||||
|
||||
public class ThingPositionComparer : IComparer<Thing>
|
||||
{
|
||||
public IntVec3 rootCell;
|
||||
|
||||
public int Compare(Thing x, Thing y) => (x.Position - rootCell).LengthHorizontalSquared.CompareTo((y.Position - rootCell).LengthHorizontalSquared);
|
||||
}
|
||||
|
||||
public struct RecipeIngredientCacheValue
|
||||
{
|
||||
public IngredientCacheList thingDefs;
|
||||
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public RecipeIngredientCacheValue(IngredientCacheList thingDefs)
|
||||
{
|
||||
this.thingDefs = thingDefs;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class WorkGiver_HaulOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PotentialWorkThingsGlobal_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
|
||||
public static IEnumerable<Thing> Postfix(IEnumerable<Thing> __result, Pawn pawn)
|
||||
{
|
||||
if (pawn != null && __result is List<Thing> list)
|
||||
Sort(list, pawn);
|
||||
return __result;
|
||||
}
|
||||
private static void Sort(List<Thing> list, Pawn pawn)
|
||||
{
|
||||
_comparer.rootCell = pawn.Position;
|
||||
list.Sort(_comparer);
|
||||
|
||||
//Log.ErrorOnce(list.Select(t => t.Position.DistanceToSquared(pawn.Position)).ToStringSafeEnumerable(), 84736285);
|
||||
}
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2022 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;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Buildings : ClassWithFishPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Get Things from the ListerThings dictionary with a rather quick lookup instead of looping over every single element in the lists of listerBuildings
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Thing> ThingsOfDefFast(ListerThings listerThings, ThingDef def) => listerThings.listsByDef.TryGetValue(def) ?? ListerThings.EmptyList;
|
||||
|
||||
public class ColonistsHaveBuilding_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveBuilding(default(ThingDef));
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
__result = ContainsDefForColonists(lister, def);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColonistsHaveResearchBench_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveResearchBench();
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
foreach (var def in LazyInit.ResearchBenches)
|
||||
{
|
||||
if (ContainsDefForColonists(lister, def))
|
||||
{
|
||||
__result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static class LazyInit
|
||||
{
|
||||
public static ThingDef[] ResearchBenches = DefDatabase<ThingDef>.AllDefsListForReading.Where(def => def.thingClass == typeof(Building_ResearchBench)).ToArray();
|
||||
static LazyInit() { }
|
||||
}
|
||||
}
|
||||
|
||||
public class ColonistsHaveBuildingWithPowerOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveBuildingWithPowerOn(null);
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == playerFaction
|
||||
&& things[i].TryGetComp<CompPowerTrader>() is not { PowerOn: false })
|
||||
{
|
||||
__result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class AllBuildingsColonistOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.AllBuildingsColonistOfDef(null);
|
||||
public static bool Prefix(ListerBuildings __instance, ref IEnumerable<Building> __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
__result = AllBuildingsColonistOfDefReplacement(lister, def);
|
||||
return false;
|
||||
}
|
||||
public static IEnumerable<Building> AllBuildingsColonistOfDefReplacement(ListerThings lister, ThingDef def)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i] is Building building)
|
||||
{
|
||||
if (building.Faction == playerFaction)
|
||||
yield return building;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowWarning(def, things[i]);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AllBuildingsNonColonistOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.AllBuildingsNonColonistOfDef(null);
|
||||
public static bool Prefix(ListerBuildings __instance, ref IEnumerable<Building> __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
__result = AllBuildingsNonColonistOfDefReplacement(lister, def);
|
||||
return false;
|
||||
}
|
||||
public static IEnumerable<Building> AllBuildingsNonColonistOfDefReplacement(ListerThings lister, ThingDef def)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i] is Building building)
|
||||
{
|
||||
if (building.Faction != playerFaction)
|
||||
yield return building;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowWarning(def, things[i]);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if harmonyIsFailingOnThisOne
|
||||
public class AllBuildingsColonistOfClass_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethodExpression => () => default(ListerBuildings).AllBuildingsColonistOfClass<Building>();
|
||||
public static bool Prefix(ListerBuildings __instance, MethodBase __originalMethod, ref IEnumerable __result)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
var type = __originalMethod.GetGenericArguments()[0]; //<-- This always returns Building instead of the type it's actually meant to look for
|
||||
DebugLog.Message($"AllBuildingsColonistOfClass running for type {type.Name}");
|
||||
if (!Replacements.TryGetValue(type, out var replacement))
|
||||
Replacements.Add(type, (GenericReplacementInterface)Activator.CreateInstance(typeof(GenericReplacement<>).MakeGenericType(type)));
|
||||
|
||||
__result = replacement.Replacement(lister);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Dictionary<Type, GenericReplacementInterface> Replacements { get; } = new();
|
||||
public interface GenericReplacementInterface
|
||||
{
|
||||
public IEnumerable Replacement(ListerThings lister);
|
||||
}
|
||||
public class GenericReplacement<T> : GenericReplacementInterface where T : Building
|
||||
{
|
||||
public IEnumerable Replacement(ListerThings lister)
|
||||
{
|
||||
buildingsOfClass ??= DefDatabase<ThingDef>.AllDefsListForReading.Where(def => def.thingClass == typeof(T)).ToArray();
|
||||
foreach (var def in buildingsOfClass)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == Faction.OfPlayerSilentFail)
|
||||
yield return things[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ThingDef[] buildingsOfClass;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public class Map_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerBuildings optimizations. Without this patch mods like Colony Manager throw errors during loading";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Constructor(typeof(Map));
|
||||
public static void Postfix(Map __instance) => _tempMap = __instance;
|
||||
}
|
||||
|
||||
private static Map? _tempMap; //mapComps get constructed before maps are added to Game.Maps, so this makes the patches work there too
|
||||
|
||||
public static void ThrowWarning(ThingDef def, Thing thing) => Log.Warning($"Tried to get building of def {def}, but its type is {thing.GetType()} instead of Building");
|
||||
|
||||
public static ListerThings? GetListerThings(ListerBuildings listerBuildings)
|
||||
{
|
||||
var maps = Current.Game.Maps;
|
||||
for (var i = 0; i < maps.Count; i++)
|
||||
{
|
||||
if (maps[i].listerBuildings == listerBuildings)
|
||||
return maps[i].listerThings;
|
||||
}
|
||||
|
||||
return _tempMap!.listerBuildings == listerBuildings ? _tempMap!.listerThings : null;
|
||||
}
|
||||
|
||||
public static bool ContainsDefForColonists(ListerThings listerThings, ThingDef def)
|
||||
{
|
||||
if (!listerThings.listsByDef.TryGetValue(def, out var things))
|
||||
return false;
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == playerFaction)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
global using HaulablesCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.ListerHaulables, PerformanceFish.Listers.Haulables.FishHaulableInfo>;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Haulables : ClassWithFishPatches
|
||||
{
|
||||
public class MapPreTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Throttling of the ListerHaulablesTick. This marks whether items should be moved from one stockpile to another. " +
|
||||
"When throttled these checks get spread out more evenly, helping against spikes that can happen with large deep storage containers.";
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(Map)!.MapPreTick();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes.MethodReplacer(new ListerHaulables(null).ListerHaulablesTick, ThreadedOrThrottled_ListerHaulablesTick);
|
||||
|
||||
public static void ThreadedOrThrottled_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
MainThread = Environment.CurrentManagedThreadId;
|
||||
|
||||
if (false && FishSettings.ThreadingEnabled)
|
||||
Threaded_ListerHaulablesTick(instance);
|
||||
else
|
||||
Throttled_ListerHaulablesTick(instance);
|
||||
}
|
||||
|
||||
//disabled
|
||||
public static void Threaded_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
DequeueQueuedThings(instance);
|
||||
|
||||
var currentCellCheckCount = GetCellCheckCount(instance);
|
||||
if (currentCellCheckCount is null)
|
||||
return;
|
||||
|
||||
if (_currentTask != null && !_currentTask.IsCompleted)
|
||||
{
|
||||
if (currentCellCheckCount.value > 1)
|
||||
currentCellCheckCount.value--;
|
||||
if (Volatile.Read(ref _queue) == 0)
|
||||
{
|
||||
Interlocked.Increment(ref _queue);
|
||||
_currentTask = _currentTask.ContinueWith(t =>
|
||||
{
|
||||
if (t.Exception is { } exception)
|
||||
Log.Error(exception.ToString());
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
_skippedTicks = 0;
|
||||
Interlocked.Decrement(ref _queue);
|
||||
ListerHaulablesTick_Patch.CurrentCellCheckCount = currentCellCheckCount.value;
|
||||
instance.ListerHaulablesTick();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_skippedTicks >= 9)
|
||||
{
|
||||
Log.Warning("Skipped 10 or more ListerHaulables ticks. This often means there's some kind of hauling error happening");
|
||||
_skippedTicks = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_skippedTicks++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentCellCheckCount.value < ListerHaulables.CellsPerTick)
|
||||
currentCellCheckCount.value++;
|
||||
}
|
||||
|
||||
if (_currentTask?.Exception is { } exception)
|
||||
Log.Error(exception.ToString());
|
||||
|
||||
_currentTask = Task.Run(() =>
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
_skippedTicks = 0;
|
||||
ListerHaulablesTick_Patch.CurrentCellCheckCount = currentCellCheckCount.value;
|
||||
instance.ListerHaulablesTick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void DequeueQueuedThings(ListerHaulables instance)
|
||||
{
|
||||
while (ThingsToRemove.TryDequeue(out var thing))
|
||||
TryRemove(instance, thing);
|
||||
|
||||
while (ThingsToAdd.TryDequeue(out var thing))
|
||||
TryAdd(instance, thing);
|
||||
}
|
||||
|
||||
public static void Throttled_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
var cellCheckCount = GetCellCheckCount(instance);
|
||||
if (cellCheckCount is null)
|
||||
return;
|
||||
|
||||
ListerHaulablesTick_Patch.CurrentCellCheckCount = cellCheckCount.value;
|
||||
Stopwatch.Start();
|
||||
try
|
||||
{
|
||||
instance.ListerHaulablesTick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex.ToString());
|
||||
}
|
||||
Stopwatch.Stop();
|
||||
if (Stopwatch.ElapsedTicks > 4000L)
|
||||
{
|
||||
if (cellCheckCount.value > 0)
|
||||
cellCheckCount.value--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cellCheckCount.value < 4)
|
||||
cellCheckCount.value++;
|
||||
}
|
||||
Stopwatch.Reset();
|
||||
}
|
||||
|
||||
private static ListerHaulablesTick_Patch.CellCheckCount? GetCellCheckCount(ListerHaulables instance)
|
||||
{
|
||||
var groupCycleIndex = ListerHaulables.groupCycleIndex + 1;
|
||||
if (groupCycleIndex >= int.MaxValue)
|
||||
groupCycleIndex = 0;
|
||||
if (groupCycleIndex == _lastGroupCycleIndex)
|
||||
{
|
||||
groupCycleIndex++;
|
||||
if (groupCycleIndex >= int.MaxValue)
|
||||
groupCycleIndex = 0;
|
||||
}
|
||||
_lastGroupCycleIndex = groupCycleIndex;
|
||||
|
||||
var allGroupsListForReading = instance.map.haulDestinationManager.AllGroupsListForReading;
|
||||
if (allGroupsListForReading.Count == 0)
|
||||
return null;
|
||||
|
||||
var num = groupCycleIndex % allGroupsListForReading.Count;
|
||||
var slotGroup = allGroupsListForReading[num];
|
||||
return slotGroup.CellsList.Count == 0 ? null
|
||||
: ListerHaulablesTick_Patch.CellCheckCountsBySlotGroup.GetOrCreateValue(slotGroup);
|
||||
}
|
||||
public static int MainThread { get; private set; }
|
||||
public static ConcurrentQueue<Thing> ThingsToRemove { get; } = new();
|
||||
public static ConcurrentQueue<Thing> ThingsToAdd { get; } = new();
|
||||
private static Stopwatch Stopwatch { get; } = new();
|
||||
|
||||
private static int _lastGroupCycleIndex;
|
||||
private static int _skippedTicks;
|
||||
private static int _queue;
|
||||
private static Task? _currentTask;
|
||||
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
|
||||
public class ListerHaulablesTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables throttling optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.ListerHaulablesTick();
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes //.MethodReplacer(GridsUtility.GetThingList, GetCopyOfThingList)
|
||||
.Manipulator(c => c.LoadsConstant(4), c =>
|
||||
{
|
||||
c.opcode = OpCodes.Call;
|
||||
c.operand = typeof(ListerHaulablesTick_Patch).GetProperty(nameof(CurrentCellCheckCount)).GetMethod;
|
||||
});
|
||||
/*public static List<Thing> GetCopyOfThingList(IntVec3 c, Map map)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
{
|
||||
PrivateThingList.ReplaceContentsWith(map.thingGrid.ThingsListAt(c));
|
||||
return PrivateThingList;
|
||||
}
|
||||
else
|
||||
{
|
||||
return map.thingGrid.ThingsListAt(c);
|
||||
}
|
||||
}*/
|
||||
public static int CurrentCellCheckCount { get; set; } = ListerHaulables.CellsPerTick;
|
||||
public static ConditionalWeakTable<SlotGroup, CellCheckCount> CellCheckCountsBySlotGroup { get; } = new();
|
||||
public class CellCheckCount
|
||||
{
|
||||
public int value = ListerHaulables.CellsPerTick;
|
||||
}
|
||||
//private static List<Thing> PrivateThingList { get; } = new();
|
||||
}
|
||||
|
||||
public static void TryAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
if (!cache.contains)
|
||||
{
|
||||
if (!instance.haulables.Contains(t))
|
||||
instance.haulables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//HaulablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThreadSafeTryAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
if (Environment.CurrentManagedThreadId == MapPreTick_Patch.MainThread || !FishSettings.ThreadingEnabled)
|
||||
{
|
||||
TryAdd(instance, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GetAndCheckHaulablesCache(instance, t).contains)
|
||||
MapPreTick_Patch.ThingsToAdd.Enqueue(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void TryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
if (cache.contains)
|
||||
{
|
||||
instance.haulables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//HaulablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThreadSafeTryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
if (Environment.CurrentManagedThreadId == MapPreTick_Patch.MainThread || !FishSettings.ThreadingEnabled)
|
||||
{
|
||||
TryRemove(instance, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetAndCheckHaulablesCache(instance, t).contains)
|
||||
MapPreTick_Patch.ThingsToRemove.Enqueue(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Check_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization for haulables checking";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.Check(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Check(ListerHaulables instance, Thing t)
|
||||
{
|
||||
//So the situation here is this: Vanilla RimWorld normally checks 4 storage cells per tick for valid haul destinations, usually with the intent of reaching higher priority storage.
|
||||
//This check itself can get quite expensive, but 4 of them aren't at all enough to be worth bothering with.
|
||||
//Now problems arise when LWM's deep storage comes into play: You put 5 stacks of items in one cell and suddenly 4 checks turn into 20.
|
||||
//20 hauling checks in turn is a number large enough to cause performance issues in colonies with high wealth.
|
||||
|
||||
if (instance.ShouldBeHaulable(t))
|
||||
ThreadSafeTryAdd(instance, t);
|
||||
else
|
||||
ThreadSafeTryRemove(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Check);
|
||||
}
|
||||
|
||||
public class TryRemove_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.TryRemove(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
if (t.def.category == ThingCategory.Item)
|
||||
ThreadSafeTryRemove(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(TryRemove);
|
||||
}
|
||||
|
||||
public class CheckAdd_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.CheckAdd(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CheckAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
|
||||
if (cache.contains || !instance.ShouldBeHaulable(t))
|
||||
return;
|
||||
|
||||
ThreadSafeTryAdd(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(CheckAdd);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FishHaulableInfo GetAndCheckHaulablesCache(ListerHaulables lister, Thing thing)
|
||||
{
|
||||
var key = new HaulablesCache(thing, lister);
|
||||
return HaulablesCache.GetValue(ref key);
|
||||
}
|
||||
|
||||
public struct FishHaulableInfo : Cache.IIsRefreshable<HaulablesCache, FishHaulableInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool contains;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public FishHaulableInfo SetNewValue(HaulablesCache key)
|
||||
{
|
||||
contains = key.Second.haulables.Contains(key.First);
|
||||
ShouldRefreshNow = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
global using MergeablesCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.ListerMergeables, PerformanceFish.Listers.Mergeables.FishMergeableInfo>;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Mergeables : ClassWithFishPatches
|
||||
{
|
||||
public class Check_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization for mergeables checking";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.Check(null);
|
||||
public static void Check(ListerMergeables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
|
||||
if (cache.contains)
|
||||
{
|
||||
if (!instance.ShouldBeMergeable(t))
|
||||
{
|
||||
instance.mergeables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (instance.ShouldBeMergeable(t))
|
||||
{
|
||||
if (!instance.mergeables.Contains(t))
|
||||
instance.mergeables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Check);
|
||||
}
|
||||
|
||||
public class CheckAdd_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerMergeables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.CheckAdd(null);
|
||||
public static void CheckAdd(ListerMergeables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
|
||||
if (cache.contains || !instance.ShouldBeMergeable(t))
|
||||
return;
|
||||
|
||||
if (!instance.mergeables.Contains(t))
|
||||
instance.mergeables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(CheckAdd);
|
||||
}
|
||||
|
||||
public class TryRemove_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerMergeables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.TryRemove(null);
|
||||
public static void TryRemove(ListerMergeables instance, Thing t)
|
||||
{
|
||||
if (t.def.category == ThingCategory.Item)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
|
||||
if (!cache.contains)
|
||||
return;
|
||||
|
||||
instance.mergeables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(TryRemove);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FishMergeableInfo GetAndCheckMergeablesCache(ListerMergeables lister, Thing thing)
|
||||
{
|
||||
var key = new MergeablesCache(thing, lister);
|
||||
return MergeablesCache.GetValue(ref key);
|
||||
}
|
||||
|
||||
public struct FishMergeableInfo : Cache.IIsRefreshable<MergeablesCache, FishMergeableInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool contains;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public FishMergeableInfo SetNewValue(MergeablesCache key)
|
||||
{
|
||||
contains = key.Second.mergeables.Contains(key.First);
|
||||
ShouldRefreshNow = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
public class Misc : ClassWithFishPatches
|
||||
{
|
||||
public class Building_Destroy_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used for haulables caching. Items don't become haulable after deconstructing stockpiles without this patch";
|
||||
public override Expression<Action> TargetMethod => () => default(Building)!.Destroy(default);
|
||||
public static void Prefix(Building __instance)
|
||||
{
|
||||
if (__instance is not IHaulDestination)
|
||||
return;
|
||||
HaulablesCache.Get.Clear();
|
||||
MergeablesCache.Get.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2022 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 StorageSettingsCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.StorageSettings, PerformanceFish.Listers.StorageSettingsPatches.FishStorageSettingsInfo>;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class StorageSettingsPatches : ClassWithFishPatches
|
||||
{
|
||||
public class AllowedToAccept_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "StorageSettings caching";
|
||||
public override Expression<Action> TargetMethod => () => default(StorageSettings)!.AllowedToAccept(default(Thing));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool Prefix(StorageSettings __instance, Thing t, ref bool __result, out bool __state)
|
||||
{
|
||||
var key = new StorageSettingsCache(t, __instance);
|
||||
ref var cache = ref StorageSettingsCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.allowedToAccept;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(StorageSettings __instance, Thing t, bool __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
StorageSettingsCache.Get[new(t, __instance)] = new() { ShouldRefreshNow = false, allowedToAccept = __result };
|
||||
}
|
||||
}
|
||||
|
||||
public class TryNotifyChanged_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "StorageSettings cache resetting";
|
||||
public override Expression<Action> TargetMethod => () => default(StorageSettings)!.TryNotifyChanged();
|
||||
public static void Postfix(StorageSettings __instance)
|
||||
{
|
||||
StorageSettingsCache.Get.Clear();
|
||||
HaulablesCache.Get.Clear();
|
||||
MergeablesCache.Get.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public struct FishStorageSettingsInfo : Cache.IIsRefreshable<StorageSettingsCache, FishStorageSettingsInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool allowedToAccept;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public FishStorageSettingsInfo SetNewValue(StorageSettingsCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
#if disabled
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Things : ClassWithFishPatches
|
||||
{
|
||||
public class ThingRequest_IsUndefined_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
|
||||
public class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
|
||||
public static bool Prefix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
return false;
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> ((FishCache)__instance.singleDef).IsSingleDef
|
||||
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
|
||||
}
|
||||
|
||||
public class Add_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Add(null);
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Remove_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Remove(null);
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Remove(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Clear_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Clear();
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class ThingsMatching_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var fishCache = (FishCache)req.singleDef;
|
||||
var cachedThings = fishCache.FilteredThings ?? ListerThings.EmptyList;
|
||||
/*if (__instance.use == ListerThingsUse.Region && cachedThings.Count > 20)
|
||||
{
|
||||
var regionalThings = GetForRegion(__instance, req);
|
||||
return regionalThings.Count < cachedThings.Count ? regionalThings : cachedThings;
|
||||
}
|
||||
else
|
||||
{
|
||||
return cachedThings;
|
||||
}*/
|
||||
return null;
|
||||
}
|
||||
/*public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req)
|
||||
=> new(TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
|
||||
(((FishCache)req.singleDef) is var cache && cache.IsSingleDef
|
||||
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null
|
||||
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn));*/
|
||||
}
|
||||
|
||||
public static CompStyleablesInfo GetCompStyleablesCache(ListerThings lister) => Cache<ListerThings, CompStyleablesInfo>.GetValue(lister);
|
||||
|
||||
public struct CompStyleablesInfo : ICanRefresh<ListerThings, CompStyleablesInfo>
|
||||
{
|
||||
public List<Thing> things;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public CompStyleablesInfo SetNewValue(ListerThings key)
|
||||
{
|
||||
if (things is null)
|
||||
things = new();
|
||||
else
|
||||
things.Clear();
|
||||
var allBuildings = key.listsByGroup[(int)ThingRequestGroup.BuildingArtificial];
|
||||
var count = allBuildings.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (allBuildings[i].TryGetComp<CompStyleable>() is { SourcePrecept: not null })
|
||||
things.Add(allBuildings[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class FishCache : ThingDef
|
||||
{
|
||||
public bool IsSingleDef => Def != null;
|
||||
public ThingDef Def { get; private set; }
|
||||
public List<Thing> FilteredThings { get; set; }
|
||||
public static FishCache ForDef(ThingDef def)
|
||||
{
|
||||
if (_dictOfDefs.TryGetValue(def, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfDefs[def] = value = new(def.defName) { Def = def };
|
||||
return value;
|
||||
}
|
||||
public static FishCache ForGroup(ThingRequestGroup group)
|
||||
{
|
||||
if (_dictOfGroups.TryGetValue(group, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfGroups[group] = value = new(group.ToString());
|
||||
return value;
|
||||
}
|
||||
public static void ClearAll()
|
||||
{
|
||||
_dictOfGroups.Clear();
|
||||
_dictOfDefs.Clear();
|
||||
}
|
||||
private FishCache(string name) => defName = name;
|
||||
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new();
|
||||
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public static class Log
|
||||
{
|
||||
public static void Message(string message) => _message(message);
|
||||
public static void Warning(string message) => _warning(message);
|
||||
public static void Error(string message) => _error(message);
|
||||
|
||||
public static class Config
|
||||
{
|
||||
public static Action<string> Message { set => _message = value; }
|
||||
public static Action<string> Warning { set => _warning = value; }
|
||||
public static Action<string> Error { set => _error = value; }
|
||||
}
|
||||
|
||||
private static Action<string> _message = Verse.Log.Message;
|
||||
private static Action<string> _warning = Verse.Log.Warning;
|
||||
private static Action<string> _error = Verse.Log.Error;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
#if disabled
|
||||
namespace PerformanceFish;
|
||||
public static class MapPawnsPatches
|
||||
{
|
||||
public static List<Pawn> GetAllPawns(MapPawns instance)
|
||||
{
|
||||
List<Pawn> allPawnsUnspawned = instance.AllPawnsUnspawned;
|
||||
if (allPawnsUnspawned.Count == 0)
|
||||
return instance.pawnsSpawned;
|
||||
instance.allPawnsResult.Clear();
|
||||
instance.allPawnsResult.AddRange(instance.pawnsSpawned);
|
||||
instance.allPawnsResult.AddRange(allPawnsUnspawned);
|
||||
return instance.allPawnsResult;
|
||||
}
|
||||
|
||||
public static List<Pawn> AllPawnsUnspawned(MapPawns instance)
|
||||
{
|
||||
instance.allPawnsUnspawnedResult.Clear();
|
||||
ThingOwnerUtility.GetAllThingsRecursively(instance.map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), instance.allPawnsUnspawnedResult, allowUnreal: true, null, alsoGetSpawnedThings: false);
|
||||
for (int num = instance.allPawnsUnspawnedResult.Count - 1; num >= 0; num--)
|
||||
{
|
||||
if (instance.allPawnsUnspawnedResult[num].Dead)
|
||||
instance.allPawnsUnspawnedResult.RemoveAt(num);
|
||||
}
|
||||
return instance.allPawnsUnspawnedResult;
|
||||
}
|
||||
|
||||
public static List<Pawn> PawnsInFaction(MapPawns instance, Faction faction)
|
||||
{
|
||||
if (faction == null)
|
||||
{
|
||||
Log.Error("Called PawnsInFaction with null faction.");
|
||||
return new List<Pawn>();
|
||||
}
|
||||
if (!instance.pawnsInFactionResult.TryGetValue(faction, out var value))
|
||||
{
|
||||
value = new List<Pawn>();
|
||||
instance.pawnsInFactionResult.Add(faction, value);
|
||||
}
|
||||
value.Clear();
|
||||
var allPawns = instance.AllPawns;
|
||||
for (var i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].Faction == faction)
|
||||
value.Add(allPawns[i]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static List<Pawn> FreeHumanlikesOfFaction(MapPawns instance, Faction faction)
|
||||
{
|
||||
if (!instance.freeHumanlikesOfFactionResult.TryGetValue(faction, out var value))
|
||||
{
|
||||
value = new List<Pawn>();
|
||||
instance.freeHumanlikesOfFactionResult.Add(faction, value);
|
||||
}
|
||||
value.Clear();
|
||||
List<Pawn> allPawns = instance.AllPawns;
|
||||
for (int i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].Faction == faction && (allPawns[i].HostFaction == null || allPawns[i].IsSlave) && allPawns[i].RaceProps.Humanlike)
|
||||
value.Add(allPawns[i]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static List<Pawn> FreeColonistsAndPrisoners(MapPawns instance)
|
||||
{
|
||||
List<Pawn> freeColonists = instance.FreeColonists;
|
||||
List<Pawn> prisonersOfColony = instance.PrisonersOfColony;
|
||||
if (prisonersOfColony.Count == 0)
|
||||
return freeColonists;
|
||||
instance.freeColonistsAndPrisonersResult.Clear();
|
||||
instance.freeColonistsAndPrisonersResult.AddRange(freeColonists);
|
||||
instance.freeColonistsAndPrisonersResult.AddRange(prisonersOfColony);
|
||||
return instance.freeColonistsAndPrisonersResult;
|
||||
}
|
||||
|
||||
public static List<Pawn> PrisonersOfColony(MapPawns instance)
|
||||
{
|
||||
instance.prisonersOfColonyResult.Clear();
|
||||
List<Pawn> allPawns = instance.AllPawns;
|
||||
for (int i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].IsPrisonerOfColony)
|
||||
instance.prisonersOfColonyResult.Add(allPawns[i]);
|
||||
}
|
||||
return instance.prisonersOfColonyResult;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2022 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 GearMassCache = PerformanceFish.Cache.ByIndex<Verse.Pawn, PerformanceFish.MassUtilityCaching.GearMassCacheValue>;
|
||||
using InventoryMassCache = PerformanceFish.Cache.ByIndex<Verse.Pawn, PerformanceFish.MassUtilityCaching.InventoryMassCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class MassUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GearMass_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches pawn gear mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup => MassUtility.GearMass;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn p, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref GearMassCache.Get.GetReference(p);
|
||||
if (cache.ShouldRefreshNow(p))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.mass;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Pawn p, bool __state, float __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
GearMassCache.Get[p] = new(__result, p);
|
||||
}
|
||||
}
|
||||
|
||||
public class InventoryMass_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches pawn inventory mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup => MassUtility.InventoryMass;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn p, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref InventoryMassCache.Get.GetReference(p);
|
||||
if (cache.ShouldRefreshNow(p))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.mass;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Pawn p, bool __state, float __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
InventoryMassCache.Get[p] = new(__result, p);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GearMassCacheValue
|
||||
{
|
||||
public float mass;
|
||||
private int _nextRefreshTick;
|
||||
private int _equipmentListsState;
|
||||
public GearMassCacheValue(float mass, Pawn p)
|
||||
{
|
||||
this.mass = mass;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_equipmentListsState = GetEquipmentListsState(p);
|
||||
}
|
||||
private static int GetEquipmentListsState(Pawn p)
|
||||
{
|
||||
var result = p.apparel != null ? p.apparel.WornApparel.Version() : 0;
|
||||
if (p.equipment != null)
|
||||
result += p.equipment.AllEquipmentListForReading.Version();
|
||||
return result;
|
||||
}
|
||||
public bool ShouldRefreshNow(Pawn p) => _nextRefreshTick < Current.Game.tickManager.TicksGame || _equipmentListsState != GetEquipmentListsState(p);
|
||||
}
|
||||
|
||||
public struct InventoryMassCacheValue
|
||||
{
|
||||
public float mass;
|
||||
private int _nextRefreshTick;
|
||||
private int _inventoryListsState;
|
||||
public InventoryMassCacheValue(float mass, Pawn p)
|
||||
{
|
||||
this.mass = mass;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 256 + Math.Abs(Rand.Int % 256);
|
||||
_inventoryListsState = GetInventoryListsState(p); //shorter refresh interval because of stack sizes not being considered by the listVersion check
|
||||
}
|
||||
private static int GetInventoryListsState(Pawn p) => p.inventory?.innerContainer is { } container ? container.innerList.Version() : 0;
|
||||
public bool ShouldRefreshNow(Pawn p) => _nextRefreshTick < Current.Game.tickManager.TicksGame || _inventoryListsState != GetInventoryListsState(p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright (c) 2022 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 System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class MiscOptimizations : ClassWithFishPatches
|
||||
{
|
||||
public class GenList_ListFullCopy_Patch : FishPatch
|
||||
{
|
||||
public override void TryUnpatch() { } // prevent unpatching
|
||||
|
||||
public override string Description => "Minor optimization. Requires a full restart to toggle off due to a bug in harmony";
|
||||
public override Delegate TargetMethodGroup => GenList.ListFullCopy<object>; // harmony patches for all T types
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(IList source, ref IList __result)
|
||||
=> source is null || source.Count == 0
|
||||
|| (__result = FisheryLib.CollectionExtensions.TryNew(source, source.GetType().GetGenericArguments()[0])) is null;
|
||||
}
|
||||
|
||||
public class WindManager_WindManagerTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Throttles plant sway to update at most once per frame instead of every tick. Also fixes the plant sway setting to actually disable plant sway calculations when it should. " +
|
||||
"By default it simply sets values to 0 but then still goes through everything, sacrificing tps for no reason";
|
||||
public override Expression<Action> TargetMethod => () => default(WindManager)!.WindManagerTick();
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes)
|
||||
{
|
||||
var find_CurrentMap = FishTranspiler.CallPropertyGetter(typeof(Find), nameof(Find.CurrentMap));
|
||||
var this_map = FishTranspiler.Field(typeof(WindManager), nameof(WindManager.map));
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
=> i - 3 >= 0
|
||||
&& codes[i - 3] == find_CurrentMap
|
||||
&& codes[i - 1] == this_map
|
||||
&& codes[i].operand is Label, // goto return;
|
||||
|
||||
code
|
||||
=> new[]
|
||||
{
|
||||
code,
|
||||
FishTranspiler.Call(ShouldSway),
|
||||
FishTranspiler.IfFalse_Short((Label)code.operand)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static bool ShouldSway()
|
||||
{
|
||||
var currentFrame = Time.frameCount;
|
||||
if (_lastSwayFrame >= currentFrame)
|
||||
return false;
|
||||
|
||||
if (Prefs.PlantWindSway != _lastPrefValue)
|
||||
_lastPrefValue = Prefs.PlantWindSway;
|
||||
else if (!Prefs.PlantWindSway)
|
||||
return false;
|
||||
|
||||
_lastSwayFrame = currentFrame;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int _lastSwayFrame;
|
||||
private static bool _lastPrefValue;
|
||||
}
|
||||
|
||||
public class DrawBatch_Flush_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization of a fleck rendering method. Low - medium performance impact";
|
||||
public override Expression<Action> TargetMethod => () => default(DrawBatch)!.Flush(default);
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions CodeInstructions, ILGenerator generator)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
|
||||
var instance_tmpPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.tmpPropertyBlocks));
|
||||
var instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange = FishTranspiler.Call<Action<HashSet<DrawBatchPropertyBlock>, List<DrawBatchPropertyBlock>>>(GenCollection.AddRange);
|
||||
var instance_tmpPropertyBlocks_key_propertyBlock_Add = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().Add);
|
||||
|
||||
var label_inFrontOf_instance_batches_Clear = generator.DefineLabel();
|
||||
var instance_batches_Clear = FishTranspiler.Call(new Dictionary<DrawBatch.BatchKey, List<DrawBatch.BatchData>>().Clear);
|
||||
|
||||
codes[
|
||||
codes.FindIndex(c => c == instance_batches_Clear)
|
||||
- 2
|
||||
].labels.Add(label_inFrontOf_instance_batches_Clear);
|
||||
|
||||
var instance_myPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.myPropertyBlocks));
|
||||
var instance_myPropertyBlocks_GetEnumerator = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().GetEnumerator);
|
||||
|
||||
var addRangeRemoved = false;
|
||||
var addRemoved = false;
|
||||
var checkFleckLeaksInserted = false;
|
||||
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (i + 4 < codes.Count
|
||||
&& codes[i + 1] == instance_tmpPropertyBlocks
|
||||
&& codes[i + 4] == instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange)
|
||||
{
|
||||
i += 4;
|
||||
addRangeRemoved = true;
|
||||
}
|
||||
else if (i + 5 < codes.Count
|
||||
&& codes[i + 1] == instance_tmpPropertyBlocks
|
||||
&& codes[i + 4] == instance_tmpPropertyBlocks_key_propertyBlock_Add
|
||||
&& codes[i + 5].opcode == Pop)
|
||||
{
|
||||
i += 5;
|
||||
addRemoved = true;
|
||||
}
|
||||
else if (i + 2 < codes.Count
|
||||
&& codes[i + 1] == instance_myPropertyBlocks
|
||||
&& codes[i + 2] == instance_myPropertyBlocks_GetEnumerator)
|
||||
{
|
||||
yield return codes[i];
|
||||
yield return FishTranspiler.Call(CheckFleckLeaks);
|
||||
yield return FishTranspiler.IfFalse(label_inFrontOf_instance_batches_Clear);
|
||||
yield return FishTranspiler.This;
|
||||
checkFleckLeaksInserted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!addRangeRemoved || !addRemoved || !checkFleckLeaksInserted)
|
||||
Log.Error($"Performance Fish failed to apply its DrawBatch.Flush() patch. Mod Settings has an option to disable this");
|
||||
}
|
||||
|
||||
public static bool CheckFleckLeaks(DrawBatch instance)
|
||||
{
|
||||
if (instance.myPropertyBlocks.Count != instance.propertyBlockCache.Count)
|
||||
{
|
||||
instance.tmpPropertyBlocks.AddRange(instance.propertyBlockCache);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if whatItsGonnaLookLikeAfterTranspiling
|
||||
public static void Flush(DrawBatch __instance, bool draw = true)
|
||||
{
|
||||
if (__instance.tmpPropertyBlock == null)
|
||||
__instance.tmpPropertyBlock = new MaterialPropertyBlock();
|
||||
__instance.tmpPropertyBlocks.Clear();
|
||||
// __instance.tmpPropertyBlocks.AddRange(__instance.propertyBlockCache); // <--------------------------------------------------------- removed
|
||||
try
|
||||
{
|
||||
foreach (var batch in __instance.batches)
|
||||
{
|
||||
var key = batch.Key;
|
||||
try
|
||||
{
|
||||
foreach (var item in batch.Value)
|
||||
{
|
||||
var batchData = item;
|
||||
if (draw)
|
||||
{
|
||||
__instance.tmpPropertyBlock.Clear();
|
||||
if (key.propertyBlock != null)
|
||||
key.propertyBlock.Write(__instance.tmpPropertyBlock);
|
||||
if (key.renderInstanced)
|
||||
{
|
||||
key.material.enableInstancing = true;
|
||||
if (batchData.hasAnyColors)
|
||||
__instance.tmpPropertyBlock.SetVectorArray("_Color", batchData.colors);
|
||||
Graphics.DrawMeshInstanced(key.mesh, 0, key.material, item.matrices, item.ptr, __instance.tmpPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.On, receiveShadows: true, key.layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < batchData.ptr; i++)
|
||||
{
|
||||
var matrix = batchData.matrices[i];
|
||||
var vector = batchData.colors[i];
|
||||
if (batchData.hasAnyColors)
|
||||
__instance.tmpPropertyBlock.SetColor("_Color", vector);
|
||||
Graphics.DrawMesh(key.mesh, matrix, key.material, key.layer, null, 0, __instance.tmpPropertyBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
batchData.Clear();
|
||||
__instance.batchDataListCache.Add(batchData);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (key.propertyBlock != null && __instance.myPropertyBlocks.Contains(key.propertyBlock))
|
||||
{
|
||||
// __instance.tmpPropertyBlocks.Add(key.propertyBlock); // <---------------------------------------------------------- removed
|
||||
key.propertyBlock.Clear();
|
||||
__instance.propertyBlockCache.Add(key.propertyBlock);
|
||||
}
|
||||
__instance.batchListCache.Add(batch.Value);
|
||||
batch.Value.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (CheckFleckLeaks(__instance)) // <------------------------------------------------------------------------------------------ added
|
||||
{
|
||||
foreach (var myPropertyBlock in __instance.myPropertyBlocks)
|
||||
{
|
||||
if (!__instance.tmpPropertyBlocks.Contains(myPropertyBlock))
|
||||
Log.Warning("Property block from FleckDrawBatch leaked!" + ((myPropertyBlock.leakDebugString == null) ? null : ("Leak debug information: \n" + myPropertyBlock.leakDebugString)));
|
||||
}
|
||||
var hashSet = __instance.myPropertyBlocks;
|
||||
__instance.myPropertyBlocks = __instance.tmpPropertyBlocks;
|
||||
__instance.tmpPropertyBlocks = hashSet;
|
||||
}
|
||||
|
||||
__instance.batches.Clear();
|
||||
__instance.lastBatchKey = default;
|
||||
__instance.lastBatchList = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class Pawn_PsychicEntropyTrackerOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class Psylink_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string? Description => "Caches/throttles the psylink method to only update every 32 ticks.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker), nameof(Pawn_PsychicEntropyTracker.Psylink));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.GetCodeInstructions(Psylink);
|
||||
|
||||
public static Hediff_Psylink Psylink(Pawn_PsychicEntropyTracker __instance)
|
||||
{
|
||||
if (__instance.psylinkCachedForTick <= Find.TickManager.TicksGame)
|
||||
{
|
||||
__instance.psylinkCached = __instance.pawn.GetMainPsylinkSource();
|
||||
__instance.psylinkCachedForTick = Find.TickManager.TicksGame + 32;
|
||||
}
|
||||
return __instance.psylinkCached;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Optimize>true</Optimize>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Authors>bradson</Authors>
|
||||
<Copyright>Copyright (c) 2021 bradson</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<EnableNETAnalyzers>True</EnableNETAnalyzers>
|
||||
<AnalysisLevel>preview-all</AnalysisLevel>
|
||||
<DebugType>none</DebugType>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<Configurations>v1.3;v1.4</Configurations>
|
||||
</PropertyGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(Configuration)' == 'v1.3'">
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\1.3\Assemblies\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" />
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.3\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition="'$(Configuration)' == 'v1.4'">
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\1.4\Assemblies\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*-*" />
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.4\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.*" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" /><!-- Version included in Unity is missing IsNullRef -->
|
||||
<PackageReference Include="Nullable" Version="1.*" />
|
||||
<PackageReference Include="IndexRange" Version="1.*" ExcludeAssets="runtime" />
|
||||
<Publicize Include="Assembly-CSharp" />
|
||||
<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.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeCast" />
|
||||
<None Include="..\..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.31911.260
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceFish", "PerformanceFish.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6948F7E6-7ED7-4716-87EF-CFF1F465F561}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
v1.3|Any CPU = v1.3|Any CPU
|
||||
v1.4|Any CPU = v1.4|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.3|Any CPU.ActiveCfg = v1.3|Any CPU
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.3|Any CPU.Build.0 = v1.3|Any CPU
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.4|Any CPU.Build.0 = v1.4|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D4907397-0B53-47A8-9B73-09C35415F989}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq.Expressions;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Threading;
|
||||
global using FisheryLib;
|
||||
global using FisheryLib.Utility.Diagnostics;
|
||||
global using HarmonyLib;
|
||||
global using PerformanceFish.Utility;
|
||||
global using RimWorld;
|
||||
global using UnityEngine;
|
||||
global using Verse;
|
||||
global using static FisheryLib.Aliases;
|
||||
global using CodeInstructions = System.Collections.Generic.IEnumerable<HarmonyLib.CodeInstruction>;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using PerformanceFish.System;
|
||||
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
[assembly: SecurityTransparent]
|
||||
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class PerformanceFishMod : Mod
|
||||
{
|
||||
public const string NAME = "Performance Fish";
|
||||
public const decimal VERSION = 0.3M;
|
||||
|
||||
#pragma warning disable IDE0079 // Visual Studio freaking out a bit over here
|
||||
#pragma warning disable CS8618
|
||||
public static FishSettings Settings { get; private set; }
|
||||
public static PerformanceFishMod Mod { get; private set; }
|
||||
public static Harmony Harmony { get; private set; }
|
||||
public static IHasFishPatch[] AllPatchClasses { get; private set; }
|
||||
#pragma warning restore CS8618, IDE0079
|
||||
|
||||
public PerformanceFishMod(ModContentPack content) : base(content)
|
||||
{
|
||||
DebugLog.Message($"Initializing {NAME} v{VERSION}");
|
||||
|
||||
if (Mod != null)
|
||||
ThrowHelper.ThrowInvalidOperationException($"Initialized a second instance of the {NAME} mod. This should never happen. Cancelling now to avoid further issues.");
|
||||
|
||||
Mod = this;
|
||||
|
||||
TryInitialize(EqualityComparerOptimization.Initialize);
|
||||
|
||||
Harmony = new(content.PackageIdPlayerFacing);
|
||||
|
||||
AllPatchClasses = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => typeof(IHasFishPatch).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
|
||||
.Select(type
|
||||
=> {
|
||||
try
|
||||
{
|
||||
return SingletonFactory<IHasFishPatch>.Get(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {type.Name}:\n{ex}");
|
||||
return null!;
|
||||
}
|
||||
})
|
||||
.Where(p => p != null)
|
||||
.ToArray();
|
||||
|
||||
ReflectionCaching.Initialize();
|
||||
|
||||
try
|
||||
{
|
||||
Settings = GetSettings<FishSettings>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an expection while trying to load its settings\n{ex}");
|
||||
WriteSettings();
|
||||
}
|
||||
|
||||
TryInitialize(AnalyzerFixes.Patch);
|
||||
|
||||
foreach (var patch in AllPatchClasses)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {patch.GetType().Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryInitialize(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {action.Method.FullDescription()}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public static T? Get<T>()
|
||||
{
|
||||
foreach (var patch in AllPatchClasses)
|
||||
{
|
||||
if (patch is T patchOfT)
|
||||
return patchOfT;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public override string SettingsCategory() => "Performance Fish";
|
||||
public override void DoSettingsWindowContents(Rect inRect) => FishSettings.DoSettingsWindowContents(inRect);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2022 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 RecipeUsersCache = PerformanceFish.Cache.ByIndex<Verse.RecipeDef, PerformanceFish.RecipeDefCaching.AllRecipeUsers_Patch.RecipeUsersCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class RecipeDefCaching : ClassWithFishPatches
|
||||
{
|
||||
public override bool RequiresLoadedGameForPatching => true;
|
||||
public class AllRecipeUsers_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the AllRecipeUsers method. Becomes impactful with lots of unfinished things around, as those check everything for potentially accepting them";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(RecipeDef), nameof(RecipeDef.AllRecipeUsers));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(RecipeDef __instance, ref IEnumerable<ThingDef> __result, out bool __state)
|
||||
{
|
||||
if (__instance is null || __instance.GetType() != typeof(RecipeDef))
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ref var cache = ref RecipeUsersCache.Get.GetReference(__instance);
|
||||
if (cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.recipeUsers;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(RecipeDef __instance, IEnumerable<ThingDef> __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
var cache = RecipeUsersCache.Get[__instance];
|
||||
|
||||
if (cache.recipeUsers is List<ThingDef> list)
|
||||
{
|
||||
list.Clear();
|
||||
list.AddRange(__result);
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.recipeUsers = new List<ThingDef>(__result);
|
||||
}
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
RecipeUsersCache.Get[__instance] = cache;
|
||||
}
|
||||
|
||||
public struct RecipeUsersCacheValue : Cache.IIsRefreshable<RecipeDef, RecipeUsersCacheValue>
|
||||
{
|
||||
public IEnumerable<ThingDef> recipeUsers;
|
||||
private int _nextRefreshFrame;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => Time.frameCount > _nextRefreshFrame;
|
||||
set => _nextRefreshFrame = value ? 0 : Time.frameCount + 4096 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public RecipeUsersCacheValue SetNewValue(RecipeDef key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/*public class PotentiallyMissingIngredients_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new RecipeDef().PotentiallyMissingIngredients(null, null);
|
||||
public static bool Prefix(RecipeDef __instance, Pawn billDoer, Map map, ref IEnumerable<ThingDef> __result)
|
||||
{
|
||||
if (!Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>.TryGetValue(__instance, billDoer, map, out var cache))
|
||||
return true;
|
||||
if (__instance.ingredients.Count != 0)
|
||||
{
|
||||
Log.Warning("RecipeDef ingredients mismatched its cache in Performance Fish, forcing a refresh");
|
||||
return true;
|
||||
}
|
||||
|
||||
__result = cache.ingredientThingDefs;
|
||||
return false;
|
||||
}
|
||||
public static void Postfix(RecipeDef __instance, Pawn billDoer, Map map, IEnumerable<ThingDef> __result, bool __runOriginal)
|
||||
{
|
||||
if (!__runOriginal)
|
||||
return;
|
||||
Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>.Get[new(__instance, billDoer, map)]
|
||||
= new() { ingredientThingDefs = __result.ToArray(), firstIngredientsItemCount = __instance.ingredients.FirstOrDefault()?.count ?? 0, ShouldRefreshNow = false };
|
||||
}
|
||||
|
||||
public static MissingIngredientsCache GetCache(RecipeDef def, Pawn billDoer, Map map) => Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>.GetValue(def, billDoer, map);
|
||||
public struct MissingIngredientsCache : ICanRefresh<Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>, MissingIngredientsCache>
|
||||
{
|
||||
public ThingDef[] ingredientThingDefs;
|
||||
private int _nextRefreshFrame;
|
||||
public int firstIngredientsItemCount;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => Time.frameCount > _nextRefreshFrame;
|
||||
set => _nextRefreshFrame = value ? 0 : Time.frameCount + 2048 + Math.Abs(Rand.Int % 1024);
|
||||
}
|
||||
public MissingIngredientsCache SetNewValue(Cache<RecipeDef, Pawn, Map, MissingIngredientsCache> key) => throw new NotImplementedException();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public class GenDrawPatches : ClassWithFishPatches
|
||||
{
|
||||
public class DrawMeshNowOrLater_Patch : FishPatch
|
||||
{
|
||||
public override Delegate TargetMethodGroup => (Action<Mesh, Vector3, Quaternion, Material, bool>)GenDraw.DrawMeshNowOrLater;
|
||||
|
||||
public static void Replacement(Mesh mesh, Vector3 loc, Quaternion quat, Material mat, bool drawNow)
|
||||
{
|
||||
if (drawNow)
|
||||
{
|
||||
if (!mat.SetPass(0))
|
||||
Log.Error("SetPass(0) call failed on material " + mat.name + " with shader " + mat.shader.name);
|
||||
Graphics.DrawMeshNow(mesh, loc, quat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DrawMeshNowOrLaterWithMatrix_Patch : FishPatch
|
||||
{
|
||||
public override Delegate TargetMethodGroup => (Action<Mesh, Matrix4x4, Material, bool>)GenDraw.DrawMeshNowOrLater;
|
||||
|
||||
public static void Replacement(Mesh mesh, Matrix4x4 matrix, Material mat, bool drawNow)
|
||||
{
|
||||
if (drawNow)
|
||||
{
|
||||
mat.SetPass(0);
|
||||
Graphics.DrawMeshNow(mesh, matrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.DrawMesh(mesh, matrix, mat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public class GraphicPatches : ClassWithFishPatches
|
||||
{
|
||||
public class DrawMeshInt_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(Graphic), nameof(Graphic.DrawMeshInt));
|
||||
|
||||
public static void Replacement(Graphic __instance, Mesh mesh, Vector3 loc, Quaternion quat, Material mat)
|
||||
{
|
||||
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public class Shadow_DrawWorker_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(Graphic_Shadow), nameof(Graphic_Shadow.DrawWorker));
|
||||
|
||||
public static void Replacement(Graphic_Shadow __instance, Vector3 loc, Rot4 rot, ThingDef thingDef, Thing thing, float extraRotation)
|
||||
{
|
||||
if (__instance.shadowMesh is null
|
||||
|| thingDef is null
|
||||
|| __instance.shadowInfo is null
|
||||
|| (Find.CurrentMap is { } map && loc.ToIntVec3().InBounds(map) && map.roofGrid.Roofed(loc.ToIntVec3()))
|
||||
|| !DebugViewSettings.drawShadows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = loc + __instance.shadowInfo.offset;
|
||||
position.y = AltitudeLayer.Shadows.AltitudeFor();
|
||||
Graphics.DrawMesh(__instance.shadowMesh, position, rot.AsQuat, MatBases.SunShadowFade, 0);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public class PawnCacheRendererPatches : ClassWithFishPatches // The PawnCacheRenderer's Camera.Render() call has a considerable performance impact despite only being used for rendering of single pawns. These are failed attempts at lowering its impact.
|
||||
{
|
||||
private static bool _optimizeCameraEnabled;
|
||||
|
||||
public class OnPostRender_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(PawnCacheRenderer), nameof(PawnCacheRenderer.OnPostRender));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(OnPostRender);
|
||||
|
||||
public static void OnPostRender(PawnCacheRenderer __instance)
|
||||
{
|
||||
Find.PawnCacheCamera.cullingMask = DefaultCullingMask;
|
||||
__instance.pawn.Drawer.renderer.RenderCache(__instance.rotation, __instance.angle, __instance.positionOffset, __instance.renderHead, __instance.renderBody, __instance.portrait, __instance.renderHeadgear, __instance.renderClothes, __instance.overrideApparelColor, __instance.overrideHairColor, __instance.stylingStation);
|
||||
Find.PawnCacheCamera.cullingMask = 16; // random
|
||||
}
|
||||
|
||||
private static int DefaultCullingMask => _defaultCullinkMask ??= Find.PawnCacheCamera.cullingMask;
|
||||
private static int? _defaultCullinkMask;
|
||||
}
|
||||
|
||||
public class RenderPawn_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(PawnCacheRenderer), nameof(PawnCacheRenderer.RenderPawn));
|
||||
|
||||
public static void Prefix(ref Vector3 cameraOffset, ref Vector3 positionOffset)
|
||||
{
|
||||
cameraOffset += new Vector3(2300f, 0f, 0f);
|
||||
positionOffset += new Vector3(2300f, 0f, 0f);
|
||||
}
|
||||
|
||||
private static int DefaultCullingMask => _defaultCullinkMask ??= Find.PawnCacheCamera.cullingMask;
|
||||
private static int? _defaultCullinkMask;
|
||||
}
|
||||
|
||||
public static bool OptimizeCameraEnabled
|
||||
{
|
||||
get => _optimizeCameraEnabled;
|
||||
set
|
||||
{
|
||||
_optimizeCameraEnabled = value;
|
||||
OptimizeCamera();
|
||||
}
|
||||
}
|
||||
|
||||
public static void OptimizeCamera()
|
||||
{
|
||||
var cacheCamera = Find.PawnCacheCamera;
|
||||
var camera = Find.Camera;
|
||||
camera.renderingPath = _optimizeCameraEnabled ? RenderingPath.VertexLit : DefaultCameraRenderingPath;
|
||||
cacheCamera.renderingPath = _optimizeCameraEnabled ? RenderingPath.VertexLit : DefaultCacheCameraRenderingPath;
|
||||
}
|
||||
|
||||
private static RenderingPath DefaultCameraRenderingPath => _defaultCameraRenderingPath ??= Find.Camera.renderingPath;
|
||||
private static RenderingPath DefaultCacheCameraRenderingPath => _defaultCacheCameraRenderingPath ??= Find.PawnCacheCamera.renderingPath;
|
||||
private static RenderingPath? _defaultCameraRenderingPath;
|
||||
private static RenderingPath? _defaultCacheCameraRenderingPath;
|
||||
}*/
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright (c) 2022 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 System.Reflection.Emit;
|
||||
using RoomOwnersCache = PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.FishRoomOwnersInfo>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
{
|
||||
public string Description => "Room stats related optimizations";
|
||||
public class Role_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Throttles room stats and role updating to happen at most once every 512 or so ticks per room, instead of every time anything at all changes or moves";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Role));
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
{
|
||||
var this_statsAndRoleDirty = FishTranspiler.Field(typeof(Room), nameof(Room.statsAndRoleDirty));
|
||||
|
||||
try
|
||||
{
|
||||
return codes.ReplaceAt((codes, i)
|
||||
=> i - 1 > 0 && codes[i - 1] == this_statsAndRoleDirty
|
||||
&& codes[i].opcode == OpCodes.Brfalse_S && codes[i].operand is Label,
|
||||
code => new[]
|
||||
{
|
||||
code,
|
||||
FishTranspiler.This,
|
||||
FishTranspiler.Call(ShouldUpdate),
|
||||
FishTranspiler.IfFalse_Short((Label)code.operand)
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish failed to patch Room.Role. This just means that specific patch will be doing nothing.\n{ex}");
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
public static bool ShouldUpdate(Room __instance)
|
||||
{
|
||||
if (__instance.role is null || !RoomThrottleInfo.TryGetValue(__instance, out var value) || value < Current.Game.tickManager.TicksGame)
|
||||
{
|
||||
RoomThrottleInfo[__instance] = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static Dictionary<Room, int> RoomThrottleInfo { get; } = new();
|
||||
static Role_Patch() => Cache.Utility.All.Add(RoomThrottleInfo);
|
||||
}
|
||||
|
||||
public class Owners_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches room owner data and throttles them to only update at most once every ~512 or so ticks. RimWorld calculates owners by checking ownership of all items contained in a room, which can get quite expensive";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Owners));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Room __instance, ref IEnumerable<Pawn> __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref RoomOwnersCache.Get.GetReference(__instance);
|
||||
|
||||
if (cache.ShouldRefreshNow)
|
||||
{
|
||||
if (__instance.TouchesMapEdge || __instance.IsHuge || (__instance.statsAndRoleDirty && !__instance.ContainedBeds.Any()))
|
||||
{
|
||||
cache.ShouldRefreshNow = false;
|
||||
cache.owners = Array.Empty<Pawn>();
|
||||
RoomOwnersCache.Get[__instance] = cache;
|
||||
__result = cache.owners;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
__result = cache.owners;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Room __instance, bool __state, ref IEnumerable<Pawn> __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
if (RoomOwnersCache.Get.TryGetValue(__instance, out var cache) && cache.owners is List<Pawn> list)
|
||||
{
|
||||
list.Clear();
|
||||
foreach (var pawn in __result)
|
||||
list.Add(pawn);
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.owners = new List<Pawn>(__result);
|
||||
}
|
||||
|
||||
cache.ShouldRefreshNow = false;
|
||||
RoomOwnersCache.Get[__instance] = cache;
|
||||
__result = cache.owners;
|
||||
}
|
||||
|
||||
public struct FishRoomOwnersInfo
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public IEnumerable<Pawn> owners;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Regions_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Minor optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Regions));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Regions_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Region> Regions_Replacement(Room instance)
|
||||
{
|
||||
var tmpRegions = instance.tmpRegions;
|
||||
var districts = instance.districts;
|
||||
lock (_lock)
|
||||
{
|
||||
tmpRegions.Clear();
|
||||
for (var i = 0; i < districts.Count; i++)
|
||||
tmpRegions.AddRange(districts[i].Regions);
|
||||
}
|
||||
return tmpRegions;
|
||||
}
|
||||
|
||||
private static readonly object _lock = new();
|
||||
}
|
||||
|
||||
public class ContainedBeds_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "So RimWorld has a list of beds and a list of all things. For some reason ludeon chose to have ContainedBeds retrieve its beds by looping through all things. This fixes that. Helps a lot against spikes";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedBeds));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ContainedBeds_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IEnumerable<Building_Bed> ContainedBeds_Replacement(Room instance)
|
||||
{
|
||||
var regions = instance.Regions;
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var beds = regions[i].ListerThings.listsByGroup[(int)ThingRequestGroup.Bed];
|
||||
if (beds is null)
|
||||
continue;
|
||||
for (var j = 0; j < beds.Count; j++)
|
||||
yield return (Building_Bed)beds[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ContainedAndAdjacentThings_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caching of room content info. Variable impact";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedAndAdjacentThings));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ContainedAndAdjacentThings_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Thing> ContainedAndAdjacentThings_Replacement(Room instance)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
uniqueContainedThings.Clear();
|
||||
|
||||
var regions = instance.Regions;
|
||||
|
||||
if (CachedThingLists.TryGetValue(instance, out var cache) && cache.listVersions.Count == regions.Count && !cache.ShouldRefreshNow)
|
||||
{
|
||||
var cacheIsAccurate = true;
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var allThings = regions[i].listerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
if (cache.listVersions[i] != -1)
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
else if (cache.listVersions[i] != allThings.Version())
|
||||
{
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
}
|
||||
if (cacheIsAccurate)
|
||||
{
|
||||
uniqueContainedThings.AddRange(cache.things);
|
||||
return uniqueContainedThings;
|
||||
}
|
||||
}
|
||||
|
||||
return RefreshContainedAndAdjacentThings(instance, regions, cache);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Thing> RefreshContainedAndAdjacentThings(Room instance, List<Region> regions, FishThingsInfo cache)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
var uniqueContainedThingsSet = instance.uniqueContainedThingsSet;
|
||||
uniqueContainedThingsSet.Clear();
|
||||
(cache.listVersions ??= new()).Clear();
|
||||
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var allThings = regions[i].ListerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
cache.listVersions.Add(-1);
|
||||
continue;
|
||||
}
|
||||
var count = allThings.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
uniqueContainedThingsSet.Add(allThings[j]);
|
||||
|
||||
cache.listVersions.Add(allThings.Version());
|
||||
}
|
||||
uniqueContainedThings.AddRange(uniqueContainedThingsSet);
|
||||
if (cache.things is List<Thing> list)
|
||||
list.ReplaceContentsWith(uniqueContainedThings);
|
||||
else
|
||||
cache.things = new List<Thing>(uniqueContainedThings);
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
CachedThingLists[instance] = cache;
|
||||
|
||||
uniqueContainedThingsSet.Clear();
|
||||
return uniqueContainedThings;
|
||||
}
|
||||
|
||||
/*private static List<Thing> RefreshContainedAndAdjacentThingsAlternative(Room instance, List<Region> regions, FishThingsInfo cache)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
(cache.listVersions ??= new()).Clear();
|
||||
var map = instance.Map;
|
||||
var regionGrid = map.regionGrid.regionGrid;
|
||||
var mapSizeX = map.cellIndices.mapSizeX;
|
||||
|
||||
var adjacentThings = new List<Thing>();
|
||||
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var region = regions[i];
|
||||
var allThings = region.ListerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
cache.listVersions.Add(-1);
|
||||
continue;
|
||||
}
|
||||
var count = allThings.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (regionGrid[CellIndicesUtility.CellToIndex(allThings[j].Position, mapSizeX)] == region)
|
||||
uniqueContainedThings.Add(allThings[j]);
|
||||
else
|
||||
adjacentThings.Add(allThings[j]);
|
||||
}
|
||||
|
||||
cache.listVersions.Add(Utility<Thing>.getListVersion(allThings));
|
||||
}
|
||||
|
||||
for (var i = 0; i < adjacentThings.Count; i++)
|
||||
{
|
||||
if (!uniqueContainedThings.Contains(adjacentThings[i]))
|
||||
uniqueContainedThings.Add(adjacentThings[i]);
|
||||
}
|
||||
|
||||
if (cache.things is List<Thing> list)
|
||||
list.ReplaceContentsWith(uniqueContainedThings);
|
||||
else
|
||||
cache.things = new List<Thing>(uniqueContainedThings);
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
CachedThingLists[instance] = cache;
|
||||
|
||||
return uniqueContainedThings;
|
||||
}*/
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static Dictionary<Room, FishThingsInfo> CachedThingLists { get; } = new();
|
||||
static ContainedAndAdjacentThings_Patch() => Cache.Utility.All.Add(CachedThingLists);
|
||||
|
||||
public struct FishThingsInfo
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public List<int> listVersions;
|
||||
public IEnumerable<Thing> things;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RoomAt_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Minor optimization through inlining ond cleaning up of duplicate calls in the method. About 1/4 faster, which isn't much, but it likely won't break anything either.";
|
||||
|
||||
public override Delegate? TargetMethodGroup => RegionAndRoomQuery.RoomAt;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(RoomAt);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All)
|
||||
{
|
||||
var mapSize = map.info.Size;
|
||||
if ((uint)c.x >= mapSize.x || (uint)c.z >= mapSize.z)
|
||||
return null;
|
||||
|
||||
var regionAndRoomUpdater = map.regionAndRoomUpdater;
|
||||
if (regionAndRoomUpdater.Enabled)
|
||||
regionAndRoomUpdater.TryRebuildDirtyRegionsAndRooms();
|
||||
else if (regionAndRoomUpdater.AnythingToRebuild)
|
||||
Log.Warning($"Trying to get valid region at {c} but RegionAndRoomUpdater is disabled. The result may be incorrect.");
|
||||
|
||||
var region = map.regionGrid.regionGrid[(c.z * mapSize.x) + c.x];
|
||||
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0 ? null
|
||||
: region.District?.Room;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class StoreUtilityPatches : ClassWithFishPatches
|
||||
{
|
||||
public class IsInValidBestStorage_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Patches IsInValidBestStorage to immediately remove things from haulables when they're found to be in the best storage. Default often has them checked several more times before removal happens";
|
||||
public override Delegate TargetMethodGroup => StoreUtility.IsInValidBestStorage;
|
||||
public static void Postfix(Thing t, bool __result)
|
||||
{
|
||||
if (__result)
|
||||
{
|
||||
t.Map.listerHaulables.TryRemove(t);
|
||||
}
|
||||
/*else if (!t.IsInValidStorage())
|
||||
{
|
||||
if (!StoreUtility.TryFindBestBetterStorageFor(t, null, t.Map, StoragePriority.Unstored, Faction.OfPlayerSilentFail, out IntVec3 _, out IHaulDestination _, needAccurateResult: false))
|
||||
{
|
||||
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
#if threadingEnabled
|
||||
public class IsGoodStoreCell_Patch : FishPatch
|
||||
{
|
||||
protected IsGoodStoreCell_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Delegate TargetMethodGroup => StoreUtility.IsGoodStoreCell;
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Enter(Lock);
|
||||
}
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Exit(Lock);
|
||||
return __exception;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
|
||||
public class GetStatValue_Patch : FishPatch
|
||||
{
|
||||
protected GetStatValue_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Delegate TargetMethodGroup => StatExtension.GetStatValue;
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Enter(Lock);
|
||||
}
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Exit(Lock);
|
||||
return __exception;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.System;
|
||||
public class EqualityComparerOptimization : ClassWithFishPatches, IHasDescription
|
||||
{
|
||||
public string Description => "Specialized EqualityComparers for slightly faster collection lookups on ValueTypes. No settings for this one. Can reduce memory usage by preventing unnecessary boxing into object.";
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
foreach (var type in AccessTools.AllTypes())
|
||||
{
|
||||
if (type.IsValueType
|
||||
&& type != typeof(void)
|
||||
&& typeof(IEquatable<>).MakeGenericType(type).IsAssignableFrom(type)
|
||||
&& type != typeof(int)
|
||||
&& type != typeof(ushort)
|
||||
&& !type.IsGenericTypeDefinition)
|
||||
{
|
||||
SetValueForDefaultComparer(type, Activator.CreateInstance(typeof(EquatableValueTypeEqualityComparer<>).MakeGenericType(type)));
|
||||
}
|
||||
}
|
||||
|
||||
SetValueForDefaultComparer(typeof(int), new IntEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(ushort), new UshortEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(string), new StringEqualityComparer());
|
||||
|
||||
// below aren't IEquatable, so why don't we just go fix that
|
||||
SetValueForDefaultComparer(typeof(IntPtr), new IntPtrEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(RuntimeFieldHandle), new RuntimeFieldHandleEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(RuntimeTypeHandle), new RuntimeTypeHandleEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(RuntimeMethodHandle), new RuntimeMethodHandleEqualityComparer());
|
||||
}
|
||||
|
||||
private static void SetValueForDefaultComparer(Type type, object value)
|
||||
=> typeof(EqualityComparer<>).MakeGenericType(type).GetField("defaultComparer", AccessTools.allDeclared).SetValue(null, value);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class EquatableValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct, IEquatable<T>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(T x, T y) => x.Equals(y);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(T obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is EquatableValueTypeEqualityComparer<T>;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class StringEqualityComparer : EqualityComparer<string>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(string x, string y) => string.Equals(x, y); // already does a nullcheck, so this skips the extra check that'd otherwise happen
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(string obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is StringEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IntPtrEqualityComparer : EqualityComparer<IntPtr>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(IntPtr x, IntPtr y) => x == y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(IntPtr obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is IntPtrEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeFieldHandleEqualityComparer : EqualityComparer<RuntimeFieldHandle>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(RuntimeFieldHandle x, RuntimeFieldHandle y) => x.Value == y.Value;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(RuntimeFieldHandle obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is RuntimeFieldHandleEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeTypeHandleEqualityComparer : EqualityComparer<RuntimeTypeHandle>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(RuntimeTypeHandle x, RuntimeTypeHandle y) => x.Value == y.Value;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(RuntimeTypeHandle obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is RuntimeTypeHandleEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeMethodHandleEqualityComparer : EqualityComparer<RuntimeMethodHandle>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(RuntimeMethodHandle x, RuntimeMethodHandle y) => x.Value == y.Value;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(RuntimeMethodHandle obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is RuntimeMethodHandleEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IntEqualityComparer : EqualityComparer<int>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(int x, int y) => x == y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(int obj) => obj;
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is IntEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UshortEqualityComparer : EqualityComparer<ushort>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(ushort x, ushort y) => x == y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(ushort obj) => obj;
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is UshortEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ReferenceEqualityComparer<T> : EqualityComparer<T>
|
||||
{
|
||||
public static new readonly ReferenceEqualityComparer<T> Default = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(T x, T y) => ReferenceEquals(x, y);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is ReferenceEqualityComparer<T>;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
using ConstructorInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.ConstructorInfo>;
|
||||
using FieldGetters = PerformanceFish.Cache.ByReference<System.RuntimeFieldHandle, PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoGetterCache>;
|
||||
using FieldInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.FieldInfo>;
|
||||
using FieldSetters = PerformanceFish.Cache.ByReference<System.RuntimeFieldHandle, PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoSetterCache>;
|
||||
using MethodInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.MethodInfo>;
|
||||
using MethodInfoWithTypesAndFlagsCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.MethodInfo>;
|
||||
using MethodInfoWithTypesCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, string, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.MethodInfo>;
|
||||
using MethodInvokers = PerformanceFish.Cache.ByReference<System.RuntimeMethodHandle, PerformanceFish.System.ReflectionCaching.MethodBasePatches.MethodInvokerCache>;
|
||||
using PropertyInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.PropertyInfo>;
|
||||
|
||||
namespace PerformanceFish.System;
|
||||
public class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public static void Initialize() // necessary to prevent recursion between patches and function pointer creation
|
||||
{
|
||||
try
|
||||
{
|
||||
ConstructorInfoCache.Initialize();
|
||||
FieldGetters.Initialize();
|
||||
FieldInfoCache.Initialize();
|
||||
FieldSetters.Initialize();
|
||||
MethodInfoCache.Initialize();
|
||||
MethodInfoWithTypesAndFlagsCache.Initialize();
|
||||
MethodInfoWithTypesCache.Initialize();
|
||||
MethodInvokers.Initialize();
|
||||
PropertyInfoCache.Initialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize its ReflectionCaching:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public struct StateAndFlags
|
||||
{
|
||||
public bool State;
|
||||
public BindingFlags Flags;
|
||||
}
|
||||
|
||||
public class TypePatches
|
||||
{
|
||||
public class GetField_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetField lookups";
|
||||
public override MethodBase TargetMethodInfo
|
||||
=> Reflection.MethodInfo("mscorlib", "System.RuntimeType", nameof(Type.GetField), new[] { typeof(string), typeof(BindingFlags) });
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref FieldInfo? __result, out StateAndFlags __state)
|
||||
{
|
||||
var key = new FieldInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
|
||||
|
||||
__result = FieldInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
if (__result is null)
|
||||
{
|
||||
__state = new() { State = true, Flags = bindingAttr };
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
__state = new() { State = false, Flags = bindingAttr };
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, FieldInfo __result, StateAndFlags __state)
|
||||
{
|
||||
if (!__state.State || __result is null)
|
||||
return;
|
||||
|
||||
FieldInfoCache.Get[new(__instance.TypeHandle, __state.Flags, ToLowerIfNeededForBindingFlags(name, __state.Flags))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetMethodWithFlags_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetMethod lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, default(BindingFlags));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref MethodInfo? __result, out bool __state)
|
||||
{
|
||||
var key = new MethodInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
|
||||
|
||||
__result = MethodInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, BindingFlags bindingAttr, MethodInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
MethodInfoCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetMethodWithTypes_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetMethod lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, Type[] types, ref MethodInfo? __result, out bool __state)
|
||||
{
|
||||
var key = new MethodInfoWithTypesCache(__instance.TypeHandle, name, new(types));
|
||||
|
||||
__result = MethodInfoWithTypesCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, Type[] types, MethodInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
MethodInfoWithTypesCache.Get[new(__instance.TypeHandle, name, new(types))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetMethodWithFlagsAndTypes_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetMethod lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, default, null, null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers, ref MethodInfo? __result, out bool __state)
|
||||
{
|
||||
if (binder != null || modifiers is { Length: > 0 })
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new MethodInfoWithTypesAndFlagsCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr), new(types));
|
||||
|
||||
__result = MethodInfoWithTypesAndFlagsCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, Type[] types, BindingFlags bindingAttr, MethodInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
MethodInfoWithTypesAndFlagsCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr), new(types))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetConstructor_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetConstructor lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetConstructor(default, null, null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers, ref ConstructorInfo? __result, out bool __state)
|
||||
{
|
||||
if (binder != null || modifiers is { Length: > 0 })
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new ConstructorInfoCache(__instance.TypeHandle, bindingAttr, new(types));
|
||||
|
||||
__result = ConstructorInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, BindingFlags bindingAttr, Type[] types, ConstructorInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
ConstructorInfoCache.Get[new(__instance.TypeHandle, bindingAttr, new(types))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetProperty_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetProperty lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetProperty(null, default(BindingFlags));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref PropertyInfo? __result, out bool __state)
|
||||
{
|
||||
var key = new PropertyInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
|
||||
|
||||
__result = PropertyInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, BindingFlags bindingAttr, PropertyInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
PropertyInfoCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
// probably never actually needed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string ToLowerIfNeededForBindingFlags(string name, BindingFlags bindingAttr) => (bindingAttr & BindingFlags.IgnoreCase) != 0 ? name.ToLowerInvariant() : name;
|
||||
}
|
||||
|
||||
public struct ByValueComparableArray<T> : IEquatable<ByValueComparableArray<T>>
|
||||
where T : class
|
||||
{
|
||||
public T[] Array;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByValueComparableArray(T[] array)
|
||||
{
|
||||
Guard.IsNotNull(array);
|
||||
Array = array;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByValueComparableArray<T> other)
|
||||
{
|
||||
var length = Array.Length;
|
||||
if (length != other.Array.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (!Array[i].Equals(other.Array[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj) => obj is ByValueComparableArray<T> array && Equals(array);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(Array.Length, Array.Length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByValueComparableArray<T> left, ByValueComparableArray<T> right) => left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByValueComparableArray<T> left, ByValueComparableArray<T> right) => !(left == right);
|
||||
}
|
||||
|
||||
#if eee == false
|
||||
public class FieldInfoPatches
|
||||
{
|
||||
public class GetValue_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes the GetValue method by invoking it through specialized cached delegates";
|
||||
|
||||
//public GetValue_Patch()
|
||||
//{
|
||||
// var types = AllSubclassesNonAbstract(typeof(FieldInfo))
|
||||
// .SelectMany(t
|
||||
// => DeclaredMethodOrNothing(t, nameof(FieldInfo.GetValue), new[] { typeof(object) }))
|
||||
// .Select(m => m.DeclaringType);
|
||||
// Log.Message(types.ToStringSafeEnumerable());
|
||||
//}
|
||||
|
||||
public override MethodBase TargetMethodInfo => Reflection.MethodInfo("mscorlib", "System.Reflection.MonoField", nameof(FieldInfo.GetValue));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object obj, ref object __result)
|
||||
{
|
||||
ref var cache = ref FieldGetters.Get.TryGetReferenceUnsafe(__instance.FieldHandle);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
if (cache.func != null || UpdateGetterCache(ref cache, __instance))
|
||||
{
|
||||
__result = cache.func!(obj);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCache(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AddCache(FieldInfo __instance)
|
||||
{
|
||||
FieldGetters.Get.Add(__instance.FieldHandle, new());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SetValue_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes the SetValue method by invoking it through specialized cached delegates";
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(FieldInfo)!.SetValue(null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object obj, object value)
|
||||
{
|
||||
ref var cache = ref FieldSetters.Get.TryGetReferenceUnsafe(__instance.FieldHandle);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
if (cache.action != null || UpdateSetterCache(ref cache, __instance))
|
||||
{
|
||||
cache.action!(obj, value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCache(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AddCache(FieldInfo __instance)
|
||||
{
|
||||
FieldSetters.Get.Add(__instance.FieldHandle, new());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UpdateGetterCache(ref FieldInfoGetterCache cache, FieldInfo instance)
|
||||
{
|
||||
cache.callCount++;
|
||||
|
||||
if (cache.callCount == 300)
|
||||
cache.func = MakeGetterDelegate(instance);
|
||||
else if (cache.callCount > 300)
|
||||
return false;
|
||||
|
||||
FieldGetters.Get[instance.FieldHandle] = cache;
|
||||
|
||||
return cache.func != null;
|
||||
}
|
||||
|
||||
public static bool UpdateSetterCache(ref FieldInfoSetterCache cache, FieldInfo instance)
|
||||
{
|
||||
cache.callCount++;
|
||||
|
||||
if (cache.callCount == 300)
|
||||
cache.action = MakeSetterDelegate(instance);
|
||||
else if (cache.callCount > 300)
|
||||
return false;
|
||||
|
||||
FieldSetters.Get[instance.FieldHandle] = cache;
|
||||
|
||||
return cache.action != null;
|
||||
}
|
||||
|
||||
private static FieldGetter? MakeGetterDelegate(FieldInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dm = new DynamicMethod($"FieldGetter_{info.Name}", typeof(object), new[] { typeof(object) }, info.DeclaringType, true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
il.Emit(FishTranspiler.This);
|
||||
|
||||
il.Emit(info.DeclaringType.IsValueType
|
||||
? FishTranspiler.Unbox(info.DeclaringType)
|
||||
: FishTranspiler.Cast(info.DeclaringType));
|
||||
}
|
||||
|
||||
il.Emit(
|
||||
/*info.DeclaringType.IsEnum
|
||||
&& info.DeclaringType.GetEnumUnderlyingType() is var enumType
|
||||
? enumType == typeof(long) || enumType == typeof(ulong)
|
||||
? FishTranspiler.Constant((long)info.GetValue(info.DeclaringType))
|
||||
: FishTranspiler.Constant((int)info.GetValue(info.DeclaringType))
|
||||
:*/ info.IsLiteral && !info.IsInitOnly ? FishTranspiler.Constant(info.GetRawConstantValue())
|
||||
: FishTranspiler.Field(info));
|
||||
|
||||
if (info.FieldType.IsValueType)
|
||||
il.Emit(FishTranspiler.Box(info.FieldType));
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (FieldGetter)dm.CreateDelegate(typeof(FieldGetter));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"PerformanceFish failed to generate an optimized delegate for {GetFieldDescription(info)}. Reverting to default behaviour instead.\n{ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FieldSetter? MakeSetterDelegate(FieldInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dm = new DynamicMethod($"FieldSetter_{info.Name}", typeof(void), new[] { typeof(object), typeof(object) }, info.DeclaringType, true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
il.Emit(FishTranspiler.This);
|
||||
|
||||
il.Emit(info.DeclaringType.IsValueType
|
||||
? FishTranspiler.Unbox(info.DeclaringType)
|
||||
: FishTranspiler.Cast(info.DeclaringType));
|
||||
}
|
||||
|
||||
il.Emit(FishTranspiler.Argument(1));
|
||||
|
||||
if (info.FieldType.IsValueType)
|
||||
{
|
||||
//il.Emit(FishTranspiler.UnboxAny(info.FieldType));
|
||||
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), info.FieldType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
|
||||
new[] { typeof(object) },
|
||||
new[] { info.FieldType.IsNullable() ? Nullable.GetUnderlyingType(info.FieldType) : info.FieldType }));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(FishTranspiler.Cast(info.FieldType));
|
||||
}
|
||||
|
||||
il.Emit(FishTranspiler.StoreField(info));
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (FieldSetter)dm.CreateDelegate(typeof(FieldSetter));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"PerformanceFish failed to generate an optimized delegate for {GetFieldDescription(info)}. Reverting to default behaviour instead.\n{ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct FieldInfoGetterCache
|
||||
{
|
||||
public FieldGetter? func;
|
||||
public int callCount;
|
||||
}
|
||||
|
||||
public struct FieldInfoSetterCache
|
||||
{
|
||||
public FieldSetter? action;
|
||||
public int callCount;
|
||||
}
|
||||
|
||||
public delegate object FieldGetter(object obj);
|
||||
|
||||
public delegate void FieldSetter(object obj, object value);
|
||||
}
|
||||
|
||||
private static string GetFieldDescription(FieldInfo info) => $"{(info.IsPublic ? "public" : "private")} {(info.IsLiteral && !info.IsInitOnly ? "const " : info.IsStatic ? "static " : "")} {info.FieldType.FullDescription()} {info.DeclaringType.FullDescription()}:{info.Name}";
|
||||
|
||||
public class MethodBasePatches
|
||||
{
|
||||
public class Invoke_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes the Invoke method by invoking it through specialized cached delegates";
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(MethodBase)!.Invoke(null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(MethodBase __instance, object obj, object[] parameters, ref object __result)
|
||||
{
|
||||
if (__instance is not MethodInfo method)
|
||||
return true;
|
||||
|
||||
ref var cache = ref MethodInvokers.Get.TryGetReferenceUnsafe(method.MethodHandle);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
if (cache.func != null || UpdateCache(ref cache, method))
|
||||
{
|
||||
__result = cache.func!(obj, parameters);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCache(method);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AddCache(MethodInfo method)
|
||||
{
|
||||
MethodInvokers.Get.Add(method.MethodHandle, new());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UpdateCache(ref MethodInvokerCache cache, MethodInfo instance)
|
||||
{
|
||||
cache.callCount++;
|
||||
if (cache.callCount == 300)
|
||||
cache.func = MakeInvokeDelegate(instance);
|
||||
else if (cache.callCount > 300)
|
||||
return false;
|
||||
|
||||
MethodInvokers.Get[instance.MethodHandle] = cache;
|
||||
|
||||
return cache.func != null;
|
||||
}
|
||||
|
||||
// HarmonyLib.MethodInvoker can't handle arrays with null values when used with ByRef value types. That aside this is near identical.
|
||||
public static MethodInvoker? MakeInvokeDelegate(MethodInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
//DebugLog.Message($"Making delegate for {(info.IsStatic ? "static" : "")} {info.DeclaringType} {info} with arguments {info.GetParameters().ToStringSafeEnumerable()}");
|
||||
|
||||
var dm = new DynamicMethod($"MethodInvoker_{info.Name}", typeof(object), new[] { typeof(object), typeof(object[]) }, info.DeclaringType, true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
var parameters = info.GetParameters();
|
||||
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
il.Emit(FishTranspiler.This);
|
||||
if (info.DeclaringType.IsValueType)
|
||||
il.Emit(FishTranspiler.UnboxAny(info.DeclaringType));
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
il.Emit(FishTranspiler.Argument(1));
|
||||
il.Emit(FishTranspiler.Constant(i));
|
||||
|
||||
var parameterType = parameters[i].ParameterType;
|
||||
|
||||
if (parameterType.IsByRef)
|
||||
{
|
||||
parameterType = parameterType.GetElementType();
|
||||
|
||||
il.Emit(FishTranspiler.LoadElementAddress<object>());
|
||||
|
||||
il.Emit(parameterType.IsValueType
|
||||
? FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
|
||||
new[] { typeof(object).MakeByRefType() },
|
||||
new[] { parameterType.IsNullable() ? Nullable.GetUnderlyingType(parameterType) : parameterType })
|
||||
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely),
|
||||
new[] { typeof(object).MakeByRefType() },
|
||||
new[] { typeof(object), parameterType }));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(FishTranspiler.LoadElement<object>());
|
||||
|
||||
il.Emit(parameterType.IsValueType
|
||||
? FishTranspiler.Call(typeof(ReflectionCaching), parameterType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
|
||||
new[] { typeof(object) },
|
||||
new[] { parameterType.IsNullable() ? Nullable.GetUnderlyingType(parameterType) : parameterType })
|
||||
: FishTranspiler.As(parameterType));
|
||||
}
|
||||
}
|
||||
|
||||
il.Emit(FishTranspiler.Call(info));
|
||||
|
||||
if (info.ReturnType == typeof(void))
|
||||
il.Emit(FishTranspiler.Null);
|
||||
else if (info.ReturnType.IsValueType)
|
||||
il.Emit(FishTranspiler.Box(info.ReturnType));
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (MethodInvoker)dm.CreateDelegate(typeof(MethodInvoker));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"PerformanceFish failed to generate an optimized delegate for {info.FullDescription()}. Reverting to default behaviour instead.\n{ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct MethodInvokerCache
|
||||
{
|
||||
public MethodInvoker? func;
|
||||
public int callCount;
|
||||
}
|
||||
|
||||
public delegate object MethodInvoker(object obj, object[] parameters);
|
||||
}
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TTo CastRefSafely<TFrom, TTo>(ref TFrom from)
|
||||
=> ref from is TTo or null
|
||||
? ref Unsafe.As<TFrom, TTo>(ref from)
|
||||
: ref ThrowInvalidCastException<TTo>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TTo UnboxRefSafely<TTo>(ref object from) where TTo : struct
|
||||
=> ref from is TTo
|
||||
? ref Unsafe.Unbox<TTo>(from)
|
||||
: ref Unsafe.Unbox<TTo>(from
|
||||
= from is null
|
||||
? default
|
||||
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TTo UnboxSafely<TTo>(object from) where TTo : struct
|
||||
=> from is TTo to
|
||||
? to
|
||||
// : from is IConvertible
|
||||
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TTo? UnboxNullableSafely<TTo>(object from) where TTo : struct
|
||||
=> from is TTo to
|
||||
? to
|
||||
: from is null
|
||||
? null
|
||||
// : from is IConvertible
|
||||
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from);
|
||||
|
||||
[DoesNotReturn]
|
||||
private static ref T ThrowInvalidCastException<T>() => throw new InvalidCastException();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2022 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 IdeoForbidsCache = PerformanceFish.Cache.ByIntRefreshable<RimWorld.CompAssignableToPawn_Bed, Verse.Pawn, PerformanceFish.ThingCompCaching.IdeoForbidsCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class ThingCompCaching : ClassWithFishPatches
|
||||
{
|
||||
public class CompAssignableToPawn_Bed_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches results of CompAssignableToPawn_Bed.IdeoligionForbids";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(CompAssignableToPawn_Bed), nameof(CompAssignableToPawn_Bed.IdeoligionForbids));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(CompAssignableToPawn_Bed __instance, Pawn pawn, ref bool __result, out bool __state)
|
||||
{
|
||||
if (pawn is null)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new IdeoForbidsCache(__instance.parent.thingIDNumber, pawn.thingIDNumber);
|
||||
ref var cache = ref IdeoForbidsCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Result;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(CompAssignableToPawn_Bed __instance, Pawn pawn, bool __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
IdeoForbidsCache.Get[new(__instance, pawn)] = new(__instance, __result, pawn);
|
||||
}
|
||||
}
|
||||
|
||||
public struct IdeoForbidsCacheValue : Cache.IIsRefreshable<IdeoForbidsCache, IdeoForbidsCacheValue>
|
||||
{
|
||||
public bool Result;
|
||||
private int _nextRefreshTick;
|
||||
private int _assignedPawnsVersion;
|
||||
private int _directRelationsVersion;
|
||||
private List<Pawn> _assignedPawns;
|
||||
private List<DirectPawnRelation> _directRelations;
|
||||
|
||||
public IdeoForbidsCacheValue(CompAssignableToPawn_Bed comp, bool result, Pawn pawn)
|
||||
{
|
||||
_directRelations = pawn.relations.DirectRelations;
|
||||
_assignedPawns = comp.assignedPawns;
|
||||
_directRelationsVersion = _directRelations.Version();
|
||||
Result = result;
|
||||
_assignedPawnsVersion = _assignedPawns.Version();
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 1024 + Math.Abs(Rand.Int % 512);
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get
|
||||
=> _nextRefreshTick < Current.Game.tickManager.TicksGame
|
||||
|| _assignedPawnsVersion != _assignedPawns.Version()
|
||||
|| _directRelationsVersion != _directRelations.Version();
|
||||
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 2048 + Math.Abs(Rand.Int % 1024);
|
||||
}
|
||||
|
||||
public IdeoForbidsCacheValue SetNewValue(IdeoForbidsCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
global using AllowedDefsListCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.ThingFilter, PerformanceFish.ThingFilterPatches.AllowedDefsListCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class ThingFilterPatches : ClassWithFishPatches
|
||||
{
|
||||
public override bool RequiresLoadedGameForPatching => true;
|
||||
/*public class Allows_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ThingFilter().Allows(null as Thing);
|
||||
public static bool Prefix(ThingFilter __instance, ref bool __result, Thing t, out bool __state)
|
||||
{
|
||||
if (!Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>.TryGetValue(t, __instance, out var cache))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.allows;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(ThingFilter __instance, bool __result, Thing t, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
ThingFilterAllowsCache[new(t, __instance)] = new() { ShouldRefreshNow = false, allows = __result };
|
||||
}
|
||||
public static Dictionary<Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>, FishThingFilterInfo> ThingFilterAllowsCache => Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>.Get;
|
||||
public struct FishThingFilterInfo : ICanRefresh<Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>, FishThingFilterInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool allows;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public FishThingFilterInfo SetNewValue(Cache<Thing, ThingFilter, FishThingFilterInfo> key) => throw new NotImplementedException();
|
||||
}
|
||||
}*/
|
||||
|
||||
public class AllowedThingDefs_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Small optimization. Returns a list instead of hashset for better performance when enumerating";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingFilter), nameof(ThingFilter.AllowedThingDefs));
|
||||
public static IEnumerable<ThingDef> Replacement(ThingFilter instance) => AllowedDefsListCache.GetValue(instance).defs;
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes) => Reflection.GetCodeInstructions(Replacement);
|
||||
}
|
||||
|
||||
public class AllStorableThingDefs_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Small optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingFilter), nameof(ThingFilter.AllStorableThingDefs));
|
||||
public static IEnumerable<ThingDef> Replacement()
|
||||
{
|
||||
var allDefs = DefDatabase<ThingDef>.AllDefsListForReading;
|
||||
var count = allDefs.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (allDefs[i].EverStorable(true))
|
||||
yield return allDefs[i];
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(Replacement);
|
||||
}
|
||||
|
||||
public class CopyAllowancesFrom_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.CopyAllowancesFrom(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Prefix(ThingFilter __instance) => AllowedDefsListCache.GetValue(__instance).defs.Clear();
|
||||
}
|
||||
|
||||
public class SetAllow_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.SetAllow(default(ThingDef), default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Prefix(ThingFilter __instance, ThingDef thingDef, bool allow)
|
||||
{
|
||||
if (__instance.allowedDefs is null)
|
||||
return;
|
||||
|
||||
ref var cache = ref AllowedDefsListCache.GetValue(__instance);
|
||||
|
||||
if (allow)
|
||||
{
|
||||
if (!__instance.allowedDefs.Contains(thingDef))
|
||||
{
|
||||
var index = ~Array.BinarySearch(cache.defs._items, 0, cache.defs.Count, thingDef, Comparer);
|
||||
//var index = ~cache.defs.BinarySearch(thingDef, Comparer);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
cache.defs.Insert(index, thingDef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (__instance.allowedDefs.Contains(thingDef))
|
||||
cache.defs.Remove(thingDef);
|
||||
}
|
||||
}
|
||||
|
||||
private static Comparer<ThingDef> Comparer { get; set; }
|
||||
= Comparer<ThingDef>.Create(ComparerMethod);
|
||||
|
||||
private static int ComparerMethod(ThingDef a, ThingDef b)
|
||||
=> a.BaseMarketValue.CompareTo(b.BaseMarketValue);
|
||||
}
|
||||
|
||||
public class SetDisallowAll_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.SetDisallowAll(null, null);
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingFilter __instance) => ForceSynchronizeCache(__instance);
|
||||
}
|
||||
|
||||
public class SetAllowAll_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.SetAllowAll(null, default);
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingFilter __instance) => ForceSynchronizeCache(__instance);
|
||||
}
|
||||
|
||||
public static void ForceSynchronizeCache(ThingFilter filter) => AllowedDefsListCache.GetValueAndRefreshNow(filter);
|
||||
|
||||
public struct AllowedDefsListCacheValue : Cache.IIsRefreshable<ThingFilter, AllowedDefsListCacheValue>
|
||||
{
|
||||
public List<ThingDef> defs;
|
||||
private int _nextRefreshTick;
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 16384 + Math.Abs(Rand.Int % 4096);
|
||||
}
|
||||
public AllowedDefsListCacheValue SetNewValue(ThingFilter key)
|
||||
{
|
||||
var allowedDefs = key.allowedDefs;
|
||||
allowedDefs.Remove(null); // apparently happens
|
||||
|
||||
if (defs is null)
|
||||
defs = new(allowedDefs);
|
||||
else
|
||||
defs.ReplaceContentsWith(allowedDefs);
|
||||
|
||||
SortDefs(defs); // why, actually? I don't remember
|
||||
defs.Version()++;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void SortDefs(List<ThingDef> defs)
|
||||
{
|
||||
var defCount = defs.Count;
|
||||
var defArray = defs._items;
|
||||
var marketValues = GetTempArrayForSorting(defCount);
|
||||
for (var i = 0; i < defCount; i++)
|
||||
marketValues[i] = GetMarketValueSafely(defArray[i]);
|
||||
|
||||
//Array.Sort(marketValues, defs); would require an array of perfect length, generating garbage
|
||||
|
||||
defArray.AsSpan()[..defCount].Sort(marketValues.AsSpan()[..defCount], null);
|
||||
defs._version++;
|
||||
}
|
||||
|
||||
private static float[] GetTempArrayForSorting(int defCount) => _tempArrayForSorting.Length >= defCount ? _tempArrayForSorting : _tempArrayForSorting = new float[defCount];
|
||||
private static float[] _tempArrayForSorting = Array.Empty<float>();
|
||||
|
||||
private static float GetMarketValueSafely(ThingDef def)
|
||||
{
|
||||
try
|
||||
{
|
||||
return def.BaseMarketValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return LogAndFixMarketValue(def, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static float LogAndFixMarketValue(ThingDef def, Exception ex)
|
||||
{
|
||||
Log.Warning($"Exception thrown while calculating market value for def {def.defName} from mod {def.modContentPack?.Name ?? "null"}. Attempting to fix this now so it doesn't happen again.\n{ex}");
|
||||
var recipe = StatWorker_MarketValue.CalculableRecipe(def); //<-- this is slow, but gets cached by perf optimizer. Might be worth replicating
|
||||
|
||||
if (recipe?.ingredients is { } ingredients)
|
||||
ingredients.Remove(null);
|
||||
else if (def.CostList is { } costList)
|
||||
costList.Remove(null);
|
||||
|
||||
try
|
||||
{
|
||||
return def.BaseMarketValue;
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Log.Error($"Exception thrown while calculating market value for def {def.defName} from mod {def.modContentPack?.Name ?? "null"} again. Fix failed, F.\n{ex2}");
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2022 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 ThingRequestCache = PerformanceFish.Cache.ByReferenceRefreshable<int, byte, System.RuntimeTypeHandle, PerformanceFish.ThingRequestInfo>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class ThingOwnerUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GetAllThingsRecursively_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caching of information about mostly unspawned pawns and things. Relatively large performance impact";
|
||||
public override Delegate TargetMethodGroup => ThingOwnerUtility.GetAllThingsRecursively<Thing>;
|
||||
|
||||
public static bool Prefix(Map map, ThingRequest request, IList outThings, bool allowUnreal, Predicate<IThingHolder> 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 cache = GetAndCheckCache(map, request.group, type);
|
||||
if (cache.count == listByGroup.Count)
|
||||
{
|
||||
if (ThingHolderCount(map) != cache.thingHolderCount)
|
||||
return __state = true;
|
||||
|
||||
outThings.Clear();
|
||||
outThings.TryAddRange(cache.things, type);
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
public static void Postfix(Map map, ThingRequest request, IList outThings, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
var type = outThings.GetType().GetGenericArguments()[0];
|
||||
var cache = ThingRequestCache.Get[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.ShouldRefreshNow = false;
|
||||
cache.count = map.listerThings.listsByGroup[(int)request.group]?.Count ?? -1;
|
||||
cache.thingHolderCount = ThingHolderCount(map);
|
||||
|
||||
ThingRequestCache.Get[new(map.uniqueID, (byte)request.group, type.TypeHandle)] = cache;
|
||||
}
|
||||
|
||||
public static ref ThingRequestInfo GetAndCheckCache(Map map, ThingRequestGroup group, Type type)
|
||||
{
|
||||
var key = new ThingRequestCache(map.uniqueID, (byte)group, type.TypeHandle);
|
||||
ref var cache = ref ThingRequestCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
ThingRequestCache.Get[key] = new();
|
||||
cache = ref ThingRequestCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
}
|
||||
|
||||
if (cache.ShouldRefreshNow)
|
||||
{
|
||||
cache.count = map.listerThings.listsByGroup[(int)group]?.Count ?? -1;
|
||||
cache.thingHolderCount = ThingHolderCount(map);
|
||||
cache.ShouldRefreshNow = false;
|
||||
ThingRequestCache.Get[new(map.uniqueID, (byte)group, type.TypeHandle)] = cache;
|
||||
cache.count = -1;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<ThingRequestInfo>(Unsafe.AsPointer(ref cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int ThingHolderCount(Map map) => (map.passingShipManager?.passingShips.Count ?? 0) + map.components.Count + (map.listerThings.listsByGroup[(int)ThingRequestGroup.ThingHolder]?.Count ?? 0);
|
||||
|
||||
/*public static void GetAllThingsRecursively(IThingHolder holder, List<Thing> outThings)
|
||||
{
|
||||
outThings.Clear();
|
||||
|
||||
ThingOwnerUtility.tmpStack.Clear();
|
||||
ThingOwnerUtility.tmpStack.Push(holder);
|
||||
while (ThingOwnerUtility.tmpStack.Count != 0)
|
||||
{
|
||||
IThingHolder thingHolder = ThingOwnerUtility.tmpStack.Pop();
|
||||
ThingOwner directlyHeldThings = thingHolder.GetDirectlyHeldThings();
|
||||
if (directlyHeldThings != null)
|
||||
{
|
||||
outThings.AddRange(directlyHeldThings);
|
||||
}
|
||||
|
||||
ThingOwnerUtility.tmpHolders.Clear();
|
||||
thingHolder.GetChildHolders(ThingOwnerUtility.tmpHolders);
|
||||
for (int i = 0; i < ThingOwnerUtility.tmpHolders.Count; i++)
|
||||
{
|
||||
ThingOwnerUtility.tmpStack.Push(ThingOwnerUtility.tmpHolders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ThingOwnerUtility.tmpStack.Clear();
|
||||
ThingOwnerUtility.tmpHolders.Clear();
|
||||
}
|
||||
|
||||
public static void AppendThingHoldersFromThings(List<IThingHolder> outThingsHolders, IList<Thing> container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (int count = container.Count; i < count; i++)
|
||||
{
|
||||
IThingHolder thingHolder = container[i] as IThingHolder;
|
||||
if (thingHolder != null)
|
||||
{
|
||||
outThingsHolders.Add(thingHolder);
|
||||
}
|
||||
|
||||
ThingWithComps thingWithComps = container[i] as ThingWithComps;
|
||||
if (thingWithComps == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ThingComp> allComps = thingWithComps.AllComps;
|
||||
for (int j = 0; j < allComps.Count; j++)
|
||||
{
|
||||
IThingHolder thingHolder2 = allComps[j] as IThingHolder;
|
||||
if (thingHolder2 != null)
|
||||
{
|
||||
outThingsHolders.Add(thingHolder2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public struct ThingRequestInfo : Cache.IIsRefreshable<ThingRequestCache, ThingRequestInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public int count;
|
||||
public int thingHolderCount;
|
||||
public IList things;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public ThingRequestInfo SetNewValue(ThingRequestCache key) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2022 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.Threading.Tasks;
|
||||
using SharedAltarCache = PerformanceFish.Cache.ByIndex<RimWorld.Ideo, PerformanceFish.ThoughtWorker_Precept_AltarSharingOptimization.SharedAltarCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class ThoughtWorker_Precept_AltarSharingOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class SharedAltar_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of ideology's shared altar thoughtworker, throttles it to wait a minimum of 128 ticks between refreshes and makes refreshing happen async on a different thread. Quite impactful on performance";
|
||||
public override Expression<Action> TargetMethod => () => default(ThoughtWorker_Precept_AltarSharing)!.SharedAltar(null);
|
||||
public static void AsyncThoughtWorker(ThoughtWorker_Precept_AltarSharing __instance, Pawn pawn)
|
||||
{
|
||||
var allThings = pawn.Map.listerThings.listsByGroup[(int)ThingRequestGroup.BuildingArtificial].ToArray();
|
||||
Task.Run(() =>
|
||||
{
|
||||
var newThing = SharedAltarParallel(pawn, allThings);
|
||||
lock (Lock)
|
||||
SharedAltarCache.GetDirectly[pawn.Ideo] = new(pawn, newThing);
|
||||
});
|
||||
}
|
||||
public static bool Prefix(ThoughtWorker_Precept_AltarSharing __instance, Pawn pawn, ref Thing? __result, out bool __state)
|
||||
{
|
||||
if (!pawn.Spawned)
|
||||
{
|
||||
__result = null;
|
||||
return __state = false;
|
||||
}
|
||||
SharedAltarCacheValue cache;
|
||||
var shouldStartRefreshing = false;
|
||||
lock (Lock)
|
||||
{
|
||||
if (!SharedAltarCache.GetDirectly.TryGetValue(pawn.Ideo, out cache) || (!FishSettings.ThreadingEnabled && cache.ShouldRefreshNow))
|
||||
{
|
||||
return __state = true;
|
||||
}
|
||||
else if (cache.ShouldRefreshNow)
|
||||
{
|
||||
cache.currentlyRefreshing = true;
|
||||
SharedAltarCache.GetDirectly[pawn.Ideo] = cache;
|
||||
shouldStartRefreshing = true;
|
||||
}
|
||||
}
|
||||
|
||||
__result = cache.thing;
|
||||
if (shouldStartRefreshing)
|
||||
AsyncThoughtWorker(__instance, pawn);
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(Pawn pawn, Thing __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
lock (Lock)
|
||||
SharedAltarCache.GetDirectly[pawn.Ideo] = new(pawn, __result);
|
||||
}
|
||||
public static Thing? SharedAltarParallel(Pawn pawn, Thing[] allThings)
|
||||
{
|
||||
if (!pawn.Spawned || pawn.Ideo == null)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < allThings.Length; i++)
|
||||
{
|
||||
var item = allThings[i];
|
||||
var compStyleable = item.TryGetComp<CompStyleable>();
|
||||
if (compStyleable == null || compStyleable.SourcePrecept == null || compStyleable.SourcePrecept.ideo != pawn.Ideo)
|
||||
continue;
|
||||
|
||||
var room = item.GetRoom();
|
||||
if (room == null || room.TouchesMapEdge)
|
||||
continue;
|
||||
|
||||
var containedAndAdjacentThings = room.ContainedAndAdjacentThings;
|
||||
for (var k = 0; k < containedAndAdjacentThings.Count; k++)
|
||||
{
|
||||
var containedAndAdjacentThing = containedAndAdjacentThings[k];
|
||||
if (containedAndAdjacentThing != item)
|
||||
{
|
||||
var compStyleable2 = containedAndAdjacentThing.TryGetComp<CompStyleable>();
|
||||
if (compStyleable2 != null && compStyleable2.SourcePrecept != null && compStyleable2.SourcePrecept.ideo != pawn.Ideo)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
|
||||
public struct SharedAltarCacheValue : Cache.IIsRefreshable<Ideo, SharedAltarCacheValue>
|
||||
{
|
||||
public Thing? thing;
|
||||
public bool currentlyRefreshing;
|
||||
private int _nextLateRefreshTick;
|
||||
private int _nextEarlyRefreshTick;
|
||||
private int _allStructuresListVersion;
|
||||
private ListerThings _lister;
|
||||
|
||||
public SharedAltarCacheValue(Pawn pawn, Thing? result)
|
||||
{
|
||||
thing = result;
|
||||
var ticks = Current.Game.tickManager.TicksGame;
|
||||
_nextLateRefreshTick = ticks + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_nextEarlyRefreshTick = ticks + 128 + Math.Abs(Rand.Int % 128);
|
||||
_lister = pawn.Map.listerThings;
|
||||
_allStructuresListVersion = _lister.listsByGroup[(int)ThingRequestGroup.BuildingArtificial].Version();
|
||||
currentlyRefreshing = false;
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get
|
||||
{
|
||||
var ticks = Current.Game.tickManager.TicksGame;
|
||||
return !currentlyRefreshing && (_nextLateRefreshTick < ticks
|
||||
|| (_nextEarlyRefreshTick < ticks && _allStructuresListVersion != _lister.listsByGroup[(int)ThingRequestGroup.BuildingArtificial].Version()));
|
||||
}
|
||||
set
|
||||
{
|
||||
var ticks = Current.Game.tickManager.TicksGame;
|
||||
_nextLateRefreshTick = value ? 0 : ticks + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_nextEarlyRefreshTick = value ? 0 : ticks + 128 + Math.Abs(Rand.Int % 128);
|
||||
}
|
||||
}
|
||||
|
||||
public SharedAltarCacheValue SetNewValue(Ideo key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Scribe_Deep normally creates a new instance when loading data. Scribe on this class prevents that. Also includes a try-catch block
|
||||
/// </summary>
|
||||
public class Exposable : IExposable
|
||||
{
|
||||
public IExposable Parent;
|
||||
public Exposable(IExposable @this) => Parent = @this;
|
||||
public void ExposeData()
|
||||
{
|
||||
try
|
||||
{
|
||||
Parent.ExposeData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
}
|
||||
}
|
||||
public override string ToString() => TrimName(Parent.ToString());
|
||||
public override bool Equals(object obj) => obj.Equals(Parent);
|
||||
public override int GetHashCode() => Parent.GetHashCode();
|
||||
|
||||
public static void Scribe(IExposable iexposable, string label, bool trimLabel = true)
|
||||
{
|
||||
var wrappedExposable = new Exposable(iexposable);
|
||||
Scribe_Deep.Look(ref wrappedExposable,
|
||||
trimLabel ? TrimName(label) : label,
|
||||
new[] { iexposable });
|
||||
}
|
||||
|
||||
internal static string TrimName(string name)
|
||||
=> name.Replace('+', '.') is var fullName
|
||||
&& $"{typeof(PerformanceFishMod).Namespace}." is var @namespace
|
||||
&& fullName.StartsWith(@namespace)
|
||||
? fullName.Remove(0, @namespace.Length)
|
||||
: fullName;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// Copyright (c) 2022 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.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerformanceFish.Utility;
|
||||
|
||||
public class KeyedList<TKey, TValue> : List<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>
|
||||
{
|
||||
public IList<TKey> Keys { get; }
|
||||
public IList<TValue> Values { get; }
|
||||
|
||||
public KeyedList()
|
||||
{
|
||||
Keys = new KeyCollection(this);
|
||||
Values = new ValueCollection(this);
|
||||
}
|
||||
|
||||
public KeyedList(int capacity) : base(capacity)
|
||||
{
|
||||
Keys = new KeyCollection(this);
|
||||
Values = new ValueCollection(this);
|
||||
}
|
||||
|
||||
public KeyedList(IEnumerable<KeyValuePair<TKey, TValue>> collection) : base(collection)
|
||||
{
|
||||
Keys = new KeyCollection(this);
|
||||
Values = new ValueCollection(this);
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var (itemKey, itemValue) = items[i];
|
||||
if (GetKeyComparer().Equals(itemKey, key))
|
||||
return itemValue;
|
||||
}
|
||||
|
||||
return ThrowHelperF.ThrowKeyNotFoundException<TKey, TValue>(key);
|
||||
}
|
||||
set
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
ref var item = ref items[i];
|
||||
if (GetKeyComparer().Equals(item.Key, key))
|
||||
{
|
||||
item = new(item.Key, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Add(new(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value) => Add(new(key, value));
|
||||
|
||||
public bool ContainsKey(TKey item)
|
||||
{
|
||||
Guard.IsNotNull(item);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetKeyComparer().Equals(items[i].Key, item))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ContainsValue(TValue item)
|
||||
{
|
||||
if (item is null)
|
||||
return false;
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetValueComparer().Equals(items[i].Value, item))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma warning disable CS8767 // missing nullable on IDictionary interface
|
||||
public bool TryGetValue(TKey key, out TValue? value)
|
||||
#pragma warning restore CS8767
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var index = IndexOfKey(key);
|
||||
if (index < 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = _items[index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CopyTo(TKey[] array, int arrayIndex)
|
||||
{
|
||||
CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Key;
|
||||
}
|
||||
public void CopyTo(TValue[] array, int arrayIndex)
|
||||
{
|
||||
CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Value;
|
||||
}
|
||||
|
||||
protected void CanCopyTo<W>(W[] array, int arrayIndex)
|
||||
{
|
||||
Guard.IsNotNull(array, nameof(array));
|
||||
Guard.IsGreaterThanOrEqualTo(arrayIndex, 0, nameof(arrayIndex));
|
||||
Guard.HasSizeGreaterThanOrEqualTo(array, Count + arrayIndex, nameof(array));
|
||||
}
|
||||
|
||||
public bool Remove(TKey item)
|
||||
{
|
||||
Guard.IsNotNull(item);
|
||||
|
||||
var index = IndexOfKey(item);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveValue(TValue item)
|
||||
{
|
||||
var index = IndexOfValue(item);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int IndexOfKey(TKey key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetKeyComparer().Equals(items[i].Key, key))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int IndexOfValue(TValue value)
|
||||
{
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetValueComparer().Equals(items[i].Value, value))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected class KeyCollection : IList<TKey>
|
||||
{
|
||||
private readonly KeyedList<TKey, TValue> _list;
|
||||
|
||||
public KeyCollection(KeyedList<TKey, TValue> list)
|
||||
=> _list = list;
|
||||
|
||||
public TKey this[int index]
|
||||
{
|
||||
get => _list[index].Key;
|
||||
set => ThrowHelper.ThrowNotSupportedException();
|
||||
}
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(TKey item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public void Clear() => _list.Clear();
|
||||
|
||||
public bool Contains(TKey item) => _list.ContainsKey(item);
|
||||
|
||||
public void CopyTo(TKey[] array, int arrayIndex)
|
||||
{
|
||||
_list.CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Key;
|
||||
}
|
||||
|
||||
public IEnumerator<TKey> GetEnumerator()
|
||||
{
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
yield return items[i].Key;
|
||||
}
|
||||
|
||||
public int IndexOf(TKey item) => _list.IndexOfKey(item);
|
||||
|
||||
public void Insert(int index, TKey item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public bool Remove(TKey item) => _list.Remove(item);
|
||||
|
||||
public void RemoveAt(int index) => _list.RemoveAt(index);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
protected class ValueCollection : IList<TValue>
|
||||
{
|
||||
private readonly KeyedList<TKey, TValue> _list;
|
||||
|
||||
public ValueCollection(KeyedList<TKey, TValue> list)
|
||||
=> _list = list;
|
||||
|
||||
public TValue this[int index]
|
||||
{
|
||||
get => _list[index].Value;
|
||||
set
|
||||
{
|
||||
ref var item = ref _list._items[index];
|
||||
item = new(item.Key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(TValue item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public void Clear() => _list.Clear();
|
||||
|
||||
public bool Contains(TValue item) => _list.ContainsValue(item);
|
||||
|
||||
public void CopyTo(TValue[] array, int arrayIndex)
|
||||
{
|
||||
_list.CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Value;
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator()
|
||||
{
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
yield return items[i].Value;
|
||||
}
|
||||
|
||||
public int IndexOf(TValue item) => _list.IndexOfValue(item);
|
||||
|
||||
public void Insert(int index, TValue item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public bool Remove(TValue item) => _list.RemoveValue(item);
|
||||
|
||||
public void RemoveAt(int index) => _list.RemoveAt(index);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static IEqualityComparer<TKey> GetKeyComparer() => EqualityComparer<TKey>.Default;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static IEqualityComparer<TValue> GetValueComparer() => EqualityComparer<TValue>.Default;
|
||||
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys => Keys;
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values => Values;
|
||||
|
||||
protected static class StringResources
|
||||
{
|
||||
internal const string ARG_KEY_NOT_FOUND_WITH_KEY = "The given key '{0}' was not present in the dictionary.";
|
||||
}
|
||||
|
||||
protected static class ThrowHelperF
|
||||
{
|
||||
[DoesNotReturn]
|
||||
internal static V ThrowKeyNotFoundException<T, V>(T key)
|
||||
=> throw GetKeyNotFoundException(key);
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException<T>(T key)
|
||||
// Generic key to move the boxing to the right hand side of throw
|
||||
=> throw GetKeyNotFoundException(key);
|
||||
|
||||
private static KeyNotFoundException GetKeyNotFoundException(object? key)
|
||||
=> new(string.Format(StringResources.ARG_KEY_NOT_FOUND_WITH_KEY, key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2022 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/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class UtilityPatches : ClassWithFishPatches
|
||||
{
|
||||
public class FinalizeInit_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Necessary for patches that require a loaded game to function";
|
||||
public override Expression<Action> TargetMethod => () => default(Game)!.FinalizeInit();
|
||||
public static void Postfix()
|
||||
{
|
||||
foreach (var patch in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
if (patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.PatchAll();
|
||||
}
|
||||
|
||||
Benchmarking.SwitchFrame = GenTicks.TicksGame + 6000;
|
||||
Benchmarking.EndFrame = Benchmarking.SwitchFrame + 6000;
|
||||
}
|
||||
}
|
||||
|
||||
public class ClearAllMapsAndWorld_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "A patch to make the game clear all Performance Fish caches whenever saves get unloaded, to avoid anything getting carried over to a new game by mistake";
|
||||
public override Delegate TargetMethodGroup => Verse.Profile.MemoryUtility.ClearAllMapsAndWorld;
|
||||
public static void Postfix()
|
||||
{
|
||||
Cache.Utility.Clear();
|
||||
foreach (var patch in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
if (patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.UnpatchAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user