Performance Fish

This commit is contained in:
bbradson
2022-10-24 18:24:02 +02:00
parent 594b32b4ef
commit 2efa8e466c
69 changed files with 9147 additions and 3 deletions
+185
View File
@@ -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
+468
View File
@@ -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;
}
}
}
}
+196
View File
@@ -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!);
}
}
+291
View File
@@ -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);
}
+46
View File
@@ -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;
}
}
+271
View File
@@ -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);
}
+43
View File
@@ -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
+23
View File
@@ -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
+26
View File
@@ -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);
}*/
+399
View File
@@ -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());
}
+114
View File
@@ -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");
}
}
}
+113
View File
@@ -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();
}
}
+232
View File
@@ -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;
}
}
+338
View File
@@ -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;
}
}
}
+21
View File
@@ -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();
}
}
+169
View File
@@ -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
+24
View File
@@ -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;
}
+96
View File
@@ -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);
}
}
+228
View File
@@ -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);
}
+110
View File
@@ -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;
}*/
+340
View File
@@ -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;
}
+321
View File
@@ -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));
}
}
+41
View File
@@ -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();
}
}
}
}