Performance Fish
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
[*.cs]
|
||||
|
||||
file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
# CA2211: Non-constant fields should not be visible
|
||||
dotnet_diagnostic.CA2211.severity = none
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_expression_bodied_methods = true:suggestion
|
||||
csharp_style_expression_bodied_constructors = true:suggestion
|
||||
csharp_style_expression_bodied_operators = true:suggestion
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
csharp_style_expression_bodied_indexers = true:suggestion
|
||||
csharp_style_expression_bodied_accessors = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||
csharp_style_expression_bodied_local_functions = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:silent
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
[*.cs]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.constant_should_be_all_upper.severity = suggestion
|
||||
dotnet_naming_rule.constant_should_be_all_upper.symbols = constant
|
||||
dotnet_naming_rule.constant_should_be_all_upper.style = all_upper
|
||||
|
||||
dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.severity = suggestion
|
||||
dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.symbols = private_or_protected_field
|
||||
dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.style = camel_case_with__
|
||||
|
||||
dotnet_naming_rule.public_or_internal_field_should_be_camel_case.severity = silent
|
||||
dotnet_naming_rule.public_or_internal_field_should_be_camel_case.symbols = public_or_internal_field
|
||||
dotnet_naming_rule.public_or_internal_field_should_be_camel_case.style = camel_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.constant.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constant.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_protected_field.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.private_or_protected_field.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.public_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.public_or_internal_field.applicable_accessibilities = public, internal
|
||||
dotnet_naming_symbols.public_or_internal_field.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.all_upper.required_prefix =
|
||||
dotnet_naming_style.all_upper.required_suffix =
|
||||
dotnet_naming_style.all_upper.word_separator = _
|
||||
dotnet_naming_style.all_upper.capitalization = all_upper
|
||||
|
||||
dotnet_naming_style.camel_case_with__.required_prefix = _
|
||||
dotnet_naming_style.camel_case_with__.required_suffix =
|
||||
dotnet_naming_style.camel_case_with__.word_separator =
|
||||
dotnet_naming_style.camel_case_with__.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.camel_case.required_prefix =
|
||||
dotnet_naming_style.camel_case.required_suffix =
|
||||
dotnet_naming_style.camel_case.word_separator =
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
dotnet_diagnostic.CA1805.severity = none
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:suggestion
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
[*.{cs,vb}]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_diagnostic.CA1838.severity = suggestion
|
||||
dotnet_diagnostic.CA1848.severity = suggestion
|
||||
dotnet_diagnostic.CA2215.severity = suggestion
|
||||
dotnet_style_readonly_field = true:silent
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||
dotnet_code_quality_unused_parameters = non_public:suggestion
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Xml;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public static class AnalyzerFixes
|
||||
{
|
||||
public static void Patch()
|
||||
{
|
||||
var modBase = AccessTools.Constructor(Reflection.Type("PerformanceAnalyzer", "Analyzer.Modbase"), new[] { typeof(ModContentPack) });
|
||||
if (modBase is null)
|
||||
return /*false*/;
|
||||
|
||||
var success = PerformanceFishMod.Harmony.Patch(modBase, postfix: new(methodof(XmlParser.CollectXmlData))) != null;
|
||||
|
||||
var methodTransplanting = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.MethodTransplanting", "UpdateMethod",
|
||||
new[] { typeof(Type), typeof(MethodInfo) });
|
||||
|
||||
if (methodTransplanting is null
|
||||
|| PerformanceFishMod.Harmony.Patch(methodTransplanting, transpiler: new(methodof(UpdateMethod_Transpiler))) is null)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
var panelLogs = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Panel_Logs", "RightClickDropDown");
|
||||
|
||||
if (panelLogs is null
|
||||
|| PerformanceFishMod.Harmony.Patch(panelLogs, postfix: new(methodof(PanelLogs_Postfix))) is null)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
return /*success*/;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// yes this is literally a prefix
|
||||
/// </summary>
|
||||
public static CodeInstructions UpdateMethod_Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
yield return FishTranspiler.ArgumentAddress(method, "meth");
|
||||
yield return FishTranspiler.Call(UpdateMethod_Prefix);
|
||||
foreach (var code in codes)
|
||||
yield return code;
|
||||
}
|
||||
|
||||
public static void UpdateMethod_Prefix(ref MethodInfo meth)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(meth) is { } moveNext)
|
||||
meth = moveNext;
|
||||
}
|
||||
|
||||
public static IEnumerable<FloatMenuOption> PanelLogs_Postfix(IEnumerable<FloatMenuOption> values, object log)
|
||||
{
|
||||
var method = ProfileLog_meth(log) as MethodInfo;
|
||||
var returned = false;
|
||||
foreach (var value in values)
|
||||
{
|
||||
yield return value;
|
||||
if (IsValidMethodForOverriding(method) && !returned && value.Label == "Profile the internal methods of")
|
||||
{
|
||||
returned = true;
|
||||
yield return new("Profile overriding methods of",
|
||||
() => PatchOverridesForMethod(method, (Category)GUIController_CurrentCategory()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidMethodForOverriding([NotNullWhen(true)] MethodInfo? info)
|
||||
=> info is not null
|
||||
&& (info.IsVirtual || info.IsAbstract || info.GetBaseDefinition().DeclaringType != info.DeclaringType);
|
||||
|
||||
public enum Category
|
||||
{
|
||||
Settings,
|
||||
Tick,
|
||||
Update,
|
||||
GUI,
|
||||
Modder
|
||||
}
|
||||
|
||||
public static void PatchOverridesForMethod(MethodInfo method, Category category)
|
||||
{
|
||||
if (OverridingMethodUtility.PatchedOverrides.Contains(method))
|
||||
{
|
||||
var signature = Utility_GetSignature(method, true);
|
||||
Messages.Message("Have already patched overrides for the method " + signature, MessageTypeDefOf.CautionInput, false);
|
||||
Utility_Warn("Trying to repeat patching already profiled overrides of method - " + signature);
|
||||
}
|
||||
else
|
||||
{
|
||||
PatchOverridesForMethodFull(method, category);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PatchOverridesForMethodFull(MethodInfo method, Category category)
|
||||
{
|
||||
try
|
||||
{
|
||||
OverridingMethodUtility.Generate(method, category);
|
||||
/*Task.Factory.StartNew(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
Modbase_Harmony().Patch(method, null, null, OverridingMethodUtility.OverrideProfiler);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
Utility_ReportException(e2, "Failed to patch overriding methods for " + Utility_GetSignature(method, false));
|
||||
}
|
||||
});*/
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utility_ReportException(e, "Failed to set up state to patch overriding methods");
|
||||
OverridingMethodUtility.PatchedOverrides.Remove(method);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OverridingMethodUtility
|
||||
{
|
||||
public static HashSet<MethodInfo> PatchedOverrides = new();
|
||||
|
||||
public static void ClearCaches() => PatchedOverrides.Clear();
|
||||
|
||||
public static void Generate(MethodInfo method, Category category)
|
||||
{
|
||||
PatchedOverrides.Add(method);
|
||||
|
||||
var name = $"{method.DeclaringType}:{method.Name}-overrides";
|
||||
|
||||
var methods = new HashSet<MethodInfo>();
|
||||
var subClasses = method.DeclaringType.AllSubclasses();
|
||||
|
||||
foreach (var subClass in subClasses)
|
||||
{
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared), m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
if (overridingMethod is null || overridingMethod.IsAbstract)
|
||||
continue;
|
||||
|
||||
methods.Add(overridingMethod);
|
||||
|
||||
//Log.Message($"Added method {Utility_GetSignature(overridingMethod, true)} for profiling");
|
||||
}
|
||||
|
||||
var type = DynamicTypeBuilder_CreateType(name, methods);
|
||||
var entry = Entry_Create(name, (int)category, type, false, true);
|
||||
GUIController_AddSpecificEntry((int)category, entry, type);
|
||||
GUIController_SwapToEntry(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static AccessTools.FieldRef<object, MethodBase> ProfileLog_meth
|
||||
=> _profileLog_meth
|
||||
??= AccessTools.FieldRefAccess<object, MethodBase>(Reflection.FieldInfo("PerformanceAnalyzer", "Analyzer.Profiling.ProfileLog", "meth"));
|
||||
private static AccessTools.FieldRef<object, MethodBase>? _profileLog_meth;
|
||||
|
||||
public static Func<Harmony> Modbase_Harmony
|
||||
=> _modbase_Harmony
|
||||
??= (Func<Harmony>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Modbase", "get_Harmony")
|
||||
.CreateDelegate(typeof(Func<Harmony>));
|
||||
private static Func<Harmony>? _modbase_Harmony;
|
||||
|
||||
public static Action<string, int> GUIController_AddEntry
|
||||
=> _guiController_AddEntry
|
||||
??= (Action<string, int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "AddEntry")
|
||||
.CreateDelegate(typeof(Action<string, int>));
|
||||
private static Action<string, int>? _guiController_AddEntry;
|
||||
|
||||
public static Action<string> GUIController_SwapToEntry
|
||||
=> _guiController_SwapToEntry
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "SwapToEntry")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _guiController_SwapToEntry;
|
||||
|
||||
public static Func<MethodBase, bool, string> Utility_GetSignature
|
||||
=> _utility_GetSignature
|
||||
??= (Func<MethodBase, bool, string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetSignature")
|
||||
.CreateDelegate(typeof(Func<MethodBase, bool, string>));
|
||||
private static Func<MethodBase, bool, string>? _utility_GetSignature;
|
||||
|
||||
public static Func<int> GUIController_CurrentCategory
|
||||
=> _guiController_CurrentCategory
|
||||
??= (Func<int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "get_CurrentCategory")
|
||||
.CreateDelegate(typeof(Func<int>));
|
||||
private static Func<int>? _guiController_CurrentCategory;
|
||||
|
||||
public static Action<string> Utility_Warn
|
||||
=> _utility_Warn
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Warn")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _utility_Warn;
|
||||
|
||||
public static Action<Exception, string> Utility_ReportException
|
||||
=> _utility_ReportException
|
||||
??= (Action<Exception, string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "ReportException")
|
||||
.CreateDelegate(typeof(Action<Exception, string>));
|
||||
private static Action<Exception, string>? _utility_ReportException;
|
||||
|
||||
public static Action<string> Utility_Error
|
||||
=> _utility_Error
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Error")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _utility_Error;
|
||||
|
||||
public static CodeInstructions Parse_Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
var parseMethod = FishTranspiler.Call("PerformanceAnalyzer", "Analyzer.Profiling.XmlParser", "ParseMethod");
|
||||
var errorMethod = FishTranspiler.Call("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error");
|
||||
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (codes[i] == parseMethod)
|
||||
{
|
||||
yield return new(codes[i - 2]); //Ldloc xmlNode
|
||||
yield return FishTranspiler.Call(ParseMethod_Fixed);
|
||||
yield return FishTranspiler.Call<Action<HashSet<MethodInfo>, IEnumerable<MethodInfo>>>(GenCollection.AddRange);
|
||||
i += 2; //removing OpCodes.Pop
|
||||
}
|
||||
else if (codes[i] == errorMethod)
|
||||
{
|
||||
yield return new(codes[i - 4]); //Ldloc xmlNode
|
||||
yield return FishTranspiler.Call(ErrorFix);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodInfo> ParseMethod_Fixed(string info, XmlNode node)
|
||||
=> node.NodeType == XmlNodeType.Comment
|
||||
? Enumerable.Empty<MethodInfo>()
|
||||
: ParseMethod(info);
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseMethod(string info)
|
||||
{
|
||||
var array = info.Split(':');
|
||||
if (array.Length != 2)
|
||||
throw new ArgumentException($"Method must be specified as 'Namespace.Type1.Type2:MethodName. Got {info} instead.", nameof(info));
|
||||
|
||||
var methods = Reflection.MethodsOfName(AccessTools.TypeByName(array[0]), array[1]);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
yield return method.IsGenericMethodDefinition
|
||||
? method.MakeGenericMethod(method.GetGenericArguments().Select(t => t.GetGenericParameterConstraints() is var constraints2 && constraints2.Length != 0 ? constraints2[0] : typeof(object)).ToArray())
|
||||
: method;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ErrorFix(string message, XmlNode node)
|
||||
{
|
||||
if (node.NodeType == XmlNodeType.Comment)
|
||||
return;
|
||||
|
||||
ThreadSafeLogger_Error(message);
|
||||
}
|
||||
|
||||
public static Action<string> ThreadSafeLogger_Error
|
||||
=> _threadSafeLogger_Error
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error")
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
private static Action<string>? _threadSafeLogger_Error;
|
||||
|
||||
public static Func<string, HashSet<MethodInfo>, Type> DynamicTypeBuilder_CreateType
|
||||
=> _dynamicTypeBuilder_CreateType
|
||||
??= (Func<string, HashSet<MethodInfo>, Type>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.DynamicTypeBuilder", "CreateType")
|
||||
.CreateDelegate(typeof(Func<string, HashSet<MethodInfo>, Type>));
|
||||
private static Func<string, HashSet<MethodInfo>, Type>? _dynamicTypeBuilder_CreateType;
|
||||
|
||||
public static Func<Type, bool, IEnumerable<MethodInfo>> Utility_GetTypeMethods
|
||||
=> _utility_GetTypeMethods
|
||||
??= (Func<Type, bool, IEnumerable<MethodInfo>>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetTypeMethods")
|
||||
.CreateDelegate(typeof(Func<Type, bool, IEnumerable<MethodInfo>>));
|
||||
private static Func<Type, bool, IEnumerable<MethodInfo>>? _utility_GetTypeMethods;
|
||||
|
||||
public static AccessTools.FieldRef<object, string> Entry_tip
|
||||
=> _entry_tip
|
||||
??= AccessTools.FieldRefAccess<string>(Type.GetType("Analyzer.Profiling.Entry, PerformanceAnalyzer"), "tip");
|
||||
private static AccessTools.FieldRef<object, string>? _entry_tip;
|
||||
|
||||
public static Action<int, object, Type> GUIController_AddSpecificEntry => _guiController_AddSpecificEntry ??= MakeGUIController_AddEntryMethod();
|
||||
private static Action<int, object, Type>? _guiController_AddSpecificEntry;
|
||||
private static Action<int, object, Type> MakeGUIController_AddEntryMethod()
|
||||
{
|
||||
var analyzer_Profiling_EntryType = Reflection.Type("PerformanceAnalyzer", "Analyzer.Profiling.Entry");
|
||||
if (analyzer_Profiling_EntryType == null)
|
||||
Log.Error("analyzer_Profiling_EntryType is null");
|
||||
|
||||
var gUIController_Tab = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "Tab");
|
||||
if (gUIController_Tab == null)
|
||||
Log.Error("gUIController_Tab is null");
|
||||
|
||||
var tab_entries = Reflection.FieldInfo("PerformanceAnalyzer", "Analyzer.Profiling.Tab", "entries");
|
||||
if (tab_entries == null)
|
||||
Log.Error("tab_entries is null");
|
||||
|
||||
/*var memberInfo_Name = AccessTools.PropertyGetter(typeof(MemberInfo), nameof(MemberInfo.Name));
|
||||
if (memberInfo_Name == null)
|
||||
Log.Error("memberInfo_Name is null");
|
||||
|
||||
var entry_Create = AccessTools.Method(analyzer_Profiling_EntryType, "Create");
|
||||
if (entry_Create == null)
|
||||
Log.Error("entry_Create is null");*/
|
||||
|
||||
var dictionary_Add = AccessTools.Method(typeof(Dictionary<,>).MakeGenericType(new[] { analyzer_Profiling_EntryType, typeof(Type) }),
|
||||
"Add", new[] { analyzer_Profiling_EntryType, typeof(Type) });
|
||||
if (dictionary_Add == null)
|
||||
Log.Error("dictionary_Add is null");
|
||||
|
||||
var dynamicMethod = new DynamicMethod("GUIController_AddEntryMethod", typeof(void), new[] { typeof(int), typeof(object), typeof(Type) });
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
il.Emit(FishTranspiler.Argument(0)); //Category
|
||||
il.Emit(FishTranspiler.Call(gUIController_Tab!)); //Tab(Category)
|
||||
il.Emit(FishTranspiler.Field(tab_entries!)); //tab.entries
|
||||
/*il.Emit(FishTranspiler.Argument(2)); //type
|
||||
il.Emit(FishTranspiler.Call(memberInfo_Name)); //type.Name
|
||||
il.Emit(FishTranspiler.LoadConstant(4)); //Category.Modder
|
||||
il.Emit(FishTranspiler.Argument(2)); //type
|
||||
il.Emit(FishTranspiler.LoadConstant(0)); //closeable = false
|
||||
il.Emit(FishTranspiler.LoadConstant(1)); //dynGen = true
|
||||
il.Emit(FishTranspiler.Call(entry_Create)); //Create(string, Category, Type, bool, bool)*/
|
||||
il.Emit(FishTranspiler.Argument(1)); //entry
|
||||
il.Emit(FishTranspiler.Argument(2)); //type
|
||||
il.Emit(FishTranspiler.Call(dictionary_Add!)); //entries.Add(Entry, Type)
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (Action<int, object, Type>)dynamicMethod.CreateDelegate(typeof(Action<int, object, Type>));
|
||||
}
|
||||
|
||||
public static Func<string, int, Type, bool, bool, object> Entry_Create => _entry_Create
|
||||
??= (Func<string, int, Type, bool, bool, object>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Entry", "Create").CreateDelegate(typeof(Func<string, int, Type, bool, bool, object>));
|
||||
private static Func<string, int, Type, bool, bool, object>? _entry_Create;
|
||||
|
||||
//Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality
|
||||
public static class XmlParser
|
||||
{
|
||||
public static void CollectXmlData()
|
||||
{
|
||||
foreach (var dir in ModLister.AllActiveModDirs)
|
||||
{
|
||||
var xmlFiles = dir.GetFiles("DubsAnalyzer.xml");
|
||||
if (xmlFiles.Length != 0)
|
||||
{
|
||||
foreach (var file in xmlFiles)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(file.OpenRead());
|
||||
|
||||
Parse(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterates through each child element in the document and attempts to extract method(s) from the strings inside the children
|
||||
private static void Parse(XmlDocument doc)
|
||||
=> ForEach(doc.DocumentElement.ChildNodes, node =>
|
||||
{
|
||||
var methods = new HashSet<MethodInfo>();
|
||||
var tip = string.Empty;
|
||||
var category = (int)Category.Modder;
|
||||
ForEach(node.ChildNodes, child =>
|
||||
{
|
||||
switch (child.Name.ToLowerInvariant())
|
||||
{
|
||||
case "category":
|
||||
category = (int)Enum.Parse(typeof(Category), child.InnerText, true); //child.InnerText;
|
||||
break;
|
||||
case "tooltip":
|
||||
case "tip":
|
||||
case "description":
|
||||
tip += child.InnerText;
|
||||
break;
|
||||
case "methods":
|
||||
case "method":
|
||||
AddMethodsFromChildNodes(child, methods, ParseMethod);
|
||||
break;
|
||||
case "derivedmethods":
|
||||
AddMethodsFromChildNodes(child, methods, ParseDerivedMethods);
|
||||
break;
|
||||
case "types":
|
||||
case "type":
|
||||
AddMethodsFromChildNodes(child, methods, ParseTypeMethods);
|
||||
break;
|
||||
case "derivedtypes":
|
||||
AddMethodsFromChildNodes(child, methods, ParseDerivedTypeMethods);
|
||||
break;
|
||||
case "nestedtype":
|
||||
case "nestedtypes":
|
||||
AddMethodsFromChildNodes(child, methods, ParseNestedTypeMethods);
|
||||
break;
|
||||
default:
|
||||
ThreadSafeLogger_Error($"[Analyzer] Attempting to read unknown value from a DubsAnalyzer.xml, the given input was {child.Name}, it should have been either 'methods', 'types', 'derivedMethods', 'derivedTypes', 'nestedTypes', 'category' or 'tooltip'");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var type = DynamicTypeBuilder_CreateType(node.Name, methods);
|
||||
var entry = Entry_Create(node.Name, category, type, false, true);
|
||||
if (tip != string.Empty)
|
||||
Entry_tip(entry) = tip;
|
||||
GUIController_AddSpecificEntry(category, entry, type);
|
||||
});
|
||||
|
||||
private static void AddMethodsFromChildNodes(XmlNode node, HashSet<MethodInfo> methods, Func<string, IEnumerable<MethodInfo>> func)
|
||||
=> ForEach(node.ChildNodes, childNode
|
||||
=> methods.AddRange(func(childNode.InnerText)));
|
||||
|
||||
private static void ForEach(XmlNodeList nodes, Action<XmlNode> action)
|
||||
{
|
||||
foreach (XmlNode node in nodes)
|
||||
{
|
||||
if (node.NodeType != XmlNodeType.Comment)
|
||||
action(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseDerivedMethods(string str)
|
||||
{
|
||||
foreach (var method in ParseMethod(str))
|
||||
{
|
||||
var subClasses = method.DeclaringType.AllSubclasses();
|
||||
|
||||
foreach (var subClass in subClasses)
|
||||
{
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared), m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
if (overridingMethod is null || overridingMethod.IsAbstract)
|
||||
continue;
|
||||
|
||||
yield return overridingMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseTypeMethods(string str) => Utility_GetTypeMethods(AccessTools.TypeByName(str), false);
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseNestedTypeMethods(string str)
|
||||
{
|
||||
var type = AccessTools.TypeByName(str);
|
||||
foreach (var subType in type.GetNestedTypes())
|
||||
{
|
||||
foreach (var method in Utility_GetTypeMethods(subType, false))
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseDerivedTypeMethods(string str)
|
||||
{
|
||||
var type = AccessTools.TypeByName(str);
|
||||
foreach (var subType in type.AllSubclasses())
|
||||
{
|
||||
foreach (var method in Utility_GetTypeMethods(subType, false))
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public class ByIndex<T_out> : IList<T_out?>, ICollection
|
||||
{
|
||||
private static ByIndex<T_out> _get = new();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_out>? _getThreadStatic;
|
||||
|
||||
public static ByIndex<T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_out>>();
|
||||
|
||||
public static ByIndex<T_out> GetDirectly => _get;
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
protected T_out?[] _items = Array.Empty<T_out>();
|
||||
protected object? _syncRoot;
|
||||
|
||||
public T_out? this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
ResizeIfNecessary(index);
|
||||
return _items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
ResizeIfNecessary(index);
|
||||
_items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[return: MaybeNull]
|
||||
public ref T_out GetReference(int index)
|
||||
{
|
||||
ResizeIfNecessary(index);
|
||||
return ref _items[index];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(int index, out T_out? value)
|
||||
{
|
||||
value = this[index];
|
||||
return !value!.Equals<T_out>(default!);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void ResizeIfNecessary(int checkedIndex)
|
||||
{
|
||||
if (checkedIndex >= _items.Length)
|
||||
ResizeFor(checkedIndex);
|
||||
}
|
||||
|
||||
protected void ResizeFor(int index)
|
||||
{
|
||||
Array.Resize(ref _items, Mathf.NextPowerOfTwo(index + 1));
|
||||
ResizeIfNecessary(index); // just in case
|
||||
}
|
||||
|
||||
public int Count => _items.Length;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
object ICollection.SyncRoot
|
||||
=> _syncRoot
|
||||
??= Interlocked.CompareExchange<object>(ref _syncRoot!, new(), null!);
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
[DoesNotReturn]
|
||||
public void Add(T_out? item) => throw new NotSupportedException();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_items.Length > 0)
|
||||
Array.Clear(_items, 0, _items.Length);
|
||||
}
|
||||
|
||||
public bool Contains(T_out? item) => Array.IndexOf(_items, item) >= 0;
|
||||
|
||||
public void CopyTo(T_out?[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<T_out?> GetEnumerator() => _items.AsEnumerable().GetEnumerator();
|
||||
|
||||
public int IndexOf(T_out? item) => Array.IndexOf(_items, item);
|
||||
|
||||
public void Insert(int index, T_out? item) => this[index] = item;
|
||||
|
||||
[DoesNotReturn]
|
||||
public bool Remove(T_out? item) => throw new NotSupportedException();
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < Count)
|
||||
_items[index] = default;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
|
||||
void ICollection.CopyTo(Array array, int index) => _items.CopyTo(array, index);
|
||||
}
|
||||
|
||||
public class ByIndex<T_in, T_out> : ByIndex<T_out>
|
||||
where T_in : notnull
|
||||
{
|
||||
private static ByIndex<T_in, T_out> _get = new();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_in, T_out>? _getThreadStatic;
|
||||
|
||||
public new static ByIndex<T_in, T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_in, T_out>>();
|
||||
|
||||
public new static ByIndex<T_in, T_out> GetDirectly => _get;
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
public unsafe T_out? this[T_in key]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[FunctionPointers.IndexGetter<T_in>.Default(key)];
|
||||
set => this[FunctionPointers.IndexGetter<T_in>.Default(key)] = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(T_in key, [NotNullWhen(true)] out T_out? value)
|
||||
{
|
||||
value = this[key];
|
||||
return !value!.Equals<T_out>(default!);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[return: MaybeNull]
|
||||
public unsafe ref T_out GetReference(T_in key)
|
||||
=> ref GetReference(FunctionPointers.IndexGetter<T_in>.Default(key))!;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seems slower than the dictionary based Cache.ByInt.
|
||||
/// </summary>
|
||||
public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_second, T_out>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
{
|
||||
private static ByIndex<T_first, T_second, T_out> _get = new();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_first, T_second, T_out>? _getThreadStatic;
|
||||
|
||||
public new static ByIndex<T_first, T_second, T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
|
||||
|
||||
public new static ByIndex<T_first, T_second, T_out> GetDirectly => _get;
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
public T_out? this[T_first first, T_second second]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => GetInner(first)[second];
|
||||
set => GetInner(first)[second] = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe ByIndex<T_second, T_out> GetInner(T_first first)
|
||||
{
|
||||
var firstIndex = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
ResizeIfNecessary(firstIndex);
|
||||
|
||||
ref var inner = ref _items[firstIndex];
|
||||
inner ??= new();
|
||||
|
||||
return inner!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[return: MaybeNull]
|
||||
public ref T_out GetReference(T_first first, T_second second)
|
||||
=> ref GetInner(first).GetReference(second)!;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(T_first first, T_second second, [NotNullWhen(true)] out T_out? value)
|
||||
{
|
||||
value = this[first, second];
|
||||
return !value!.Equals<T_out>(default!);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public struct ByInt<T_in, T_result>
|
||||
where T_in : notnull
|
||||
where T_result : IIsRefreshable<T_in, T_result>
|
||||
{
|
||||
private static Dictionary<ByInt<T_in, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByInt<T_in, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByInt<T_in, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_in, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByInt<T_in, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByInt() => Utility.All.Add(_get);
|
||||
|
||||
public int Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T_in key) => Key = FunctionPointers.IndexGetter<T_in>.Default(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T_result GetValue(T_in key)
|
||||
{
|
||||
ref var cache = ref Get.TryGetReferenceUnsafe(new(key));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[new(key)] = Reflection.New<T_result>();
|
||||
cache = ref Get.GetReference(new(key))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[new(key)] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int key)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int key, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(T_in key, [MaybeNullWhen(false)] out T_result value)
|
||||
=> Get.TryGetValue(new(key), out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(T_in key)
|
||||
{
|
||||
var cache = Get.TryGetValue(new(key));
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[new(key)] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByIntRefreshable<T_first, T_second, T_result> : IEquatable<ByIntRefreshable<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
where T_result : IIsRefreshable<ByIntRefreshable<T_first, T_second, T_result>, T_result>
|
||||
{
|
||||
private static EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>> _comparer = new Comparer();
|
||||
private class Comparer : EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(ByIntRefreshable<T_first, T_second, T_result> x, ByIntRefreshable<T_first, T_second, T_result> y)
|
||||
=> x.First == y.First
|
||||
&& x.Second == y.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(ByIntRefreshable<T_first, T_second, T_result> obj)
|
||||
=> HashCode.Combine(obj.First, obj.Second);
|
||||
}
|
||||
|
||||
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> _get = new(_comparer);
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>>(_comparer);
|
||||
|
||||
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByIntRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByIntRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
// CS8347 without this crap
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByIntRefreshable<T_first, T_second, T_result> key, [MaybeNullWhen(false)] out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByIntRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByIntRefreshable(T_first first, T_second second)
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByIntRefreshable(int first, int second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
|
||||
public int First;
|
||||
public int Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByIntRefreshable<T_first, T_second, T_result> other)
|
||||
=> First == other.First
|
||||
&& Second == other.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByIntRefreshable<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByInt<T_first, T_second, T_third, T_result> : IEquatable<ByInt<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
where T_result : IIsRefreshable<ByInt<T_first, T_second, T_third, T_result>, T_result>
|
||||
{
|
||||
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByInt() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ByInt<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first, int third)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByInt<T_first, T_second, T_third, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByInt<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T_first first, T_second second, T_third third)
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
|
||||
Third = FunctionPointers.IndexGetter<T_third>.Default(third);
|
||||
}
|
||||
|
||||
public int First;
|
||||
public int Second;
|
||||
public int Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T_first, T_second, T_third, T_result> other)
|
||||
=> First == other.First
|
||||
&& Second == other.Second
|
||||
&& Third == other.Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second, Third);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByInt<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public static class ByMap<T> where T : new()
|
||||
{
|
||||
private static List<(Map map, T value)> _cache = new();
|
||||
[ThreadStatic]
|
||||
private static List<(Map map, T value)>? _cacheThreadStatic;
|
||||
|
||||
public static List<(Map map, T value)> GetCache
|
||||
=> UnityData.IsInMainThread ? _cache
|
||||
: _cacheThreadStatic ??= Utility.AddNew<List<(Map map, T value)>>();
|
||||
|
||||
public static List<(Map map, T value)> GetCacheDirectly => _cache;
|
||||
|
||||
static ByMap() => Utility.All.Add(_cache);
|
||||
|
||||
private static Func<Map, T> Initialize { get; }
|
||||
= typeof(MapComponent).IsAssignableFrom(typeof(T))
|
||||
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent), Array.Empty<Type>(), new[] { typeof(T) }))
|
||||
// missing T constraints cause the compiler to complain when trying to just use map => map.GetComponent<T>
|
||||
: _ => new();
|
||||
|
||||
public static T GetFor(Map map)
|
||||
{
|
||||
var cache = GetCache;
|
||||
for (var i = 0; i < cache.Count; i++)
|
||||
{
|
||||
var cacheEntry = cache[i];
|
||||
if (cacheEntry.map == map)
|
||||
return cacheEntry.value;
|
||||
|
||||
if (Find.Maps.Count <= i + 1
|
||||
&& !Find.Maps.Contains(cacheEntry.map))
|
||||
{
|
||||
cache.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
cache.Add((map, Initialize(map)));
|
||||
return cache[^1].value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public static class ByReference<T_first, T_result>
|
||||
where T_first : notnull
|
||||
{
|
||||
private static Dictionary<T_first, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<T_first, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<T_first, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<T_first, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<T_first, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByReference<T_first, T_second, T_result> : IEquatable<ByReference<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
{
|
||||
private static Dictionary<ByReference<T_first, T_second, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReference<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_result>, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T_first, T_second, T_result>, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByReference(T_first first, T_second second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<ByReference<T_first, T_second, T_result>>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T_first, T_second, T_result>>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public T_first First;
|
||||
|
||||
public T_second Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReference<T_first, T_second, T_result> other)
|
||||
=> First.Equals<T_first>(other.First)
|
||||
&& Second.Equals<T_second>(other.Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReference<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByReference<T_first, T_second, T_result> left, ByReference<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByReference<T_first, T_second, T_result> left, ByReference<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReference<T_first, T_second, T_third, T_result> : IEquatable<ByReference<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
{
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
/*UnityData.IsInMainThread ? _get
|
||||
:*/ => _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByReference(T_first first, T_second second, T_third third)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Third = third;
|
||||
_hashCode = HashCode.Combine(first, second, third);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<ByReference<T_first, T_second, T_third, T_result>>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T_first, T_second, T_third, T_result>>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_second, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public T_first First;
|
||||
|
||||
public T_second Second;
|
||||
|
||||
public T_third Third;
|
||||
|
||||
private int _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReference<T_first, T_second, T_third, T_result> other)
|
||||
=> First.Equals<T_first>(other.First)
|
||||
&& Second.Equals<T_second>(other.Second)
|
||||
&& Third.Equals<T_third>(other.Third);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReference<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByReference<T_first, T_second, T_third, T_result> left, ByReference<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByReference<T_first, T_second, T_third, T_result> left, ByReference<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReference<T_first, T_second, T_third, T_fourth, T_result> : IEquatable<ByReference<T_first, T_second, T_third, T_fourth, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull where T_fourth : notnull
|
||||
{
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
/*UnityData.IsInMainThread ? _get
|
||||
:*/ => _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result>>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByReference<T_first, T_second, T_third, T_fourth, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReference() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByReference(T_first first, T_second second, T_third third, T_fourth fourth)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Third = third;
|
||||
Fourth = fourth;
|
||||
_hashCode = HashCode.Combine(first, second, third, fourth);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<T_fourth>.Default;
|
||||
_ = FisheryLib.FunctionPointers.Equals<ByReference<T_first, T_second, T_third, T_fourth, T_result>>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_first>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_second>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_third>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<T_fourth>.Default;
|
||||
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T_first, T_second, T_third, T_fourth, T_result>>.Default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {typeof(ByReference<T_first, T_second, T_third, T_result>).FullDescription()}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public T_first First;
|
||||
|
||||
public T_second Second;
|
||||
|
||||
public T_third Third;
|
||||
|
||||
public T_fourth Fourth;
|
||||
|
||||
private int _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReference<T_first, T_second, T_third, T_fourth, T_result> other)
|
||||
=> First.Equals<T_first>(other.First)
|
||||
&& Second.Equals<T_second>(other.Second)
|
||||
&& Third.Equals<T_third>(other.Third)
|
||||
&& Fourth.Equals<T_fourth>(other.Fourth);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _hashCode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReference<T_first, T_second, T_third, T_fourth, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByReference<T_first, T_second, T_third, T_fourth, T_result> left, ByReference<T_first, T_second, T_third, T_fourth, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByReference<T_first, T_second, T_third, T_fourth, T_result> left, ByReference<T_first, T_second, T_third, T_fourth, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public static class ByReferenceRefreshable<T_first, T_result>
|
||||
where T_first : notnull
|
||||
where T_result : IIsRefreshable<T_first, T_result>
|
||||
{
|
||||
private static Dictionary<T_first, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<T_first, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<T_first, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<T_first, T_result>>();
|
||||
|
||||
public static Dictionary<T_first, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T_result GetValue(T_first key)
|
||||
{
|
||||
ref var cache = ref Get.TryGetReferenceUnsafe(key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Get.GetReference(key)!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(T_first key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(T_first key)
|
||||
{
|
||||
var cache = Get.TryGetValue(key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByReferenceRefreshable<T_first, T_second, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>
|
||||
{
|
||||
internal ByReference<T_first, T_second, T_result> _innerValue;
|
||||
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
// CS8347 without this crap
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ByReferenceRefreshable(T_first first, T_second second) => _innerValue = new(first, second);
|
||||
|
||||
public T_first First => _innerValue.First;
|
||||
|
||||
public T_second Second => _innerValue.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_result> other)
|
||||
=> _innerValue.Equals(other._innerValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _innerValue.GetHashCode();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReferenceRefreshable<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReferenceRefreshable<T_first, T_second, T_third, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>
|
||||
{
|
||||
private ByReference<T_first, T_second, T_third, T_result> _innerValue;
|
||||
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Activator.CreateInstance<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Activator.CreateInstance<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ByReferenceRefreshable(T_first first, T_second second, T_third third)
|
||||
=> _innerValue = new(first, second, third);
|
||||
|
||||
public T_first First => _innerValue.First;
|
||||
public T_second Second => _innerValue.Second;
|
||||
public T_third Third => _innerValue.Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_third, T_result> other)
|
||||
=> _innerValue.Equals(other._innerValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _innerValue.GetHashCode();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReferenceRefreshable<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public interface IIsRefreshable<T, V> where V : IIsRefreshable<T, V>
|
||||
{
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public V SetNewValue(T key);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public static class Utility
|
||||
{
|
||||
public static ConcurrentBag<ICollection> All { get; } = new();
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
foreach (var cache in All)
|
||||
{
|
||||
cache.GetType()
|
||||
.GetMethods()
|
||||
.Where(m
|
||||
=> m.Name == nameof(Array.Clear)
|
||||
&& m.GetParameters().Length == 0)
|
||||
.First()
|
||||
.Invoke(cache, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static T AddNew<T>() where T : ICollection, new()
|
||||
{
|
||||
var newCollection = new T();
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
public static T AddNew<T>(IEqualityComparer comparer) where T : ICollection, new()
|
||||
{
|
||||
var newCollection = (T)Activator.CreateInstance(typeof(T), comparer);
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int IndexGetter<T>(T item);
|
||||
@@ -0,0 +1,645 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PerformanceFish.Collections;
|
||||
|
||||
#pragma warning disable CS8766, CS8767
|
||||
public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>
|
||||
{
|
||||
//private int[] _buckets;
|
||||
|
||||
internal Entry[] _entries;
|
||||
|
||||
private int _count;
|
||||
|
||||
private int _version;
|
||||
|
||||
//private unsafe delegate*<TKey, int> _keyGetHashCode = FunctionPointers.GetHashCode<TKey>.Default;
|
||||
//private unsafe delegate*<TKey, TKey, bool> _keyEquals = FunctionPointers.Equals<TKey>.Default;
|
||||
|
||||
//private int _freeList;
|
||||
|
||||
//private int _freeCount;
|
||||
|
||||
//private KeyCollection _keys;
|
||||
|
||||
//private ValueCollection _values;
|
||||
|
||||
public ICollection<TKey> Keys => throw new NotImplementedException();
|
||||
|
||||
public ICollection<TValue> Values => throw new NotImplementedException();
|
||||
|
||||
public int Version
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _count;
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
ICollection IDictionary.Keys => throw new NotImplementedException();
|
||||
|
||||
ICollection IDictionary.Values => throw new NotImplementedException();
|
||||
|
||||
bool IDictionary.IsFixedSize => false;
|
||||
object ICollection.SyncRoot => this; // matching System.Collections.Generic.Dictionary
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => throw new NotImplementedException();
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => throw new NotImplementedException();
|
||||
|
||||
object? IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
=> key is TKey tkey
|
||||
? this[tkey]
|
||||
: ThrowHelper.ThrowKeyNotFoundException<object, TValue>(key);
|
||||
|
||||
set
|
||||
{
|
||||
if (key is TKey tkey)
|
||||
{
|
||||
if (value is TValue tvalue)
|
||||
this[tkey] = tvalue;
|
||||
else if (value is null)
|
||||
this[tkey] = default;
|
||||
else
|
||||
ThrowHelper.ThrowWrongValueTypeArgumentException(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowWrongKeyTypeArgumentException(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TValue? this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
}
|
||||
|
||||
if (entry.Next is null)
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
ref var reference = ref TryGetReferenceUnsafe(key);
|
||||
if (!Unsafe.IsNullRef(ref reference))
|
||||
{
|
||||
reference = value;
|
||||
_version++;
|
||||
return;
|
||||
}
|
||||
|
||||
Add(key, value!);
|
||||
}
|
||||
}
|
||||
|
||||
public FishTable() : this(0) { }
|
||||
|
||||
public FishTable(int minimumCapacity)
|
||||
=> _entries = new Entry[
|
||||
minimumCapacity <= 1 ? 1 // Minimum size to not throw on indexing into _entries. Length is not checked beforehand
|
||||
: Mathf.NextPowerOfTwo(minimumCapacity)]; // System.Collections.Dictionary starts at 3. So wasteful.
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static uint FastModulo(uint first, uint second)
|
||||
=> first & (second - 1);
|
||||
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
{
|
||||
entry = entry.Next.Entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
value = entry.Value;
|
||||
return entry.Next != null;
|
||||
}
|
||||
|
||||
public TValue? TryGetValue(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
return default;
|
||||
}
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
|
||||
public ref TValue GetReference(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
}
|
||||
|
||||
if (entry.Next == null)
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
|
||||
return ref entry.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not.
|
||||
/// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns>ref TValue or Unsafe.NullRef{TValue}</returns>
|
||||
public ref TValue TryGetReferenceUnsafe(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<TValue>()!;
|
||||
}
|
||||
|
||||
return ref entry.Next == null
|
||||
? ref Unsafe.NullRef<TValue>()
|
||||
: ref entry.Value;
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return entry.Next != null;
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
if (_count == _entries.Length) // throwing DuplicateKeyExceptions happens later, so Expanding occurs based on expected
|
||||
Expand(); // behaviour instead of matching actual results like in System.Collections.Generic.
|
||||
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.Next != null)
|
||||
{
|
||||
if (entry.HashCode == keyCode && entry.Key.Equals<TKey>(key))
|
||||
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
|
||||
|
||||
if (entry.Next == EntryReference.Empty)
|
||||
break;
|
||||
|
||||
entry = ref entry.Next.Entry;
|
||||
}
|
||||
|
||||
if (entry.Next == null) // means the entry is empty
|
||||
entry = new(keyCode, key, value);
|
||||
else // if (entry.Next == _freeSlot) // occupied entry with an empty slot in entry.Next
|
||||
entry.Next = new(keyCode, key, value);
|
||||
// entry.Next != _freeSlot would be a threading issue, as this was just checked in the loop. Unhandled.
|
||||
|
||||
_count++;
|
||||
_version++;
|
||||
}
|
||||
|
||||
private void Expand()
|
||||
{
|
||||
var newLength = Mathf.NextPowerOfTwo(_entries.Length + 1);
|
||||
var newArray = new Entry[newLength];
|
||||
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Next is null)
|
||||
continue;
|
||||
|
||||
BeforeAdding:
|
||||
ref var newEntry = ref newArray[FastModulo(entry.HashCode, (uint)newLength)];
|
||||
|
||||
while (newEntry.Next != null)
|
||||
{
|
||||
if (newEntry.HashCode == entry.HashCode && newEntry.Key.Equals<TKey>(entry.Key))
|
||||
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry.Key);
|
||||
|
||||
if (newEntry.Next == EntryReference.Empty)
|
||||
break;
|
||||
|
||||
newEntry = ref newEntry.Next.Entry;
|
||||
}
|
||||
|
||||
var nextEntry = entry.Next;
|
||||
entry.Next = EntryReference.Empty;
|
||||
|
||||
if (newEntry.Next == null)
|
||||
newEntry = entry;
|
||||
else
|
||||
newEntry.Next = new(entry);
|
||||
|
||||
if (nextEntry != EntryReference.Empty)
|
||||
{
|
||||
entry = nextEntry.Entry;
|
||||
goto BeforeAdding;
|
||||
}
|
||||
}
|
||||
|
||||
_entries = newArray;
|
||||
}
|
||||
|
||||
private ref Entry TryFindEntry(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<Entry>();
|
||||
}
|
||||
|
||||
return ref entry;
|
||||
}
|
||||
|
||||
private void RemoveEntry(ref Entry entry)
|
||||
{
|
||||
entry = entry.Next != EntryReference.Empty && entry.Next != null
|
||||
? entry.Next.Entry
|
||||
: default;
|
||||
|
||||
_count--;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
ref var entry = ref TryFindEntry(key);
|
||||
if (Unsafe.IsNullRef(ref entry))
|
||||
return false;
|
||||
|
||||
RemoveEntry(ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(_entries, 0, _entries.Length);
|
||||
_count = 0;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
=> TryGetValue(item.Key, out var value)
|
||||
&& value.Equals<TValue>(item.Value);
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
var length = _entries.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Next == null)
|
||||
continue;
|
||||
|
||||
array[arrayIndex] = new(entry.Key, entry.Value);
|
||||
arrayIndex++;
|
||||
|
||||
while (entry.Next != EntryReference.Empty)
|
||||
{
|
||||
entry = entry.Next.Entry;
|
||||
array[arrayIndex] = new(entry.Key, entry.Value);
|
||||
arrayIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
ref var entry = ref TryFindEntry(item.Key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref entry)
|
||||
|| !entry.Value.Equals<TValue>(item.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveEntry(ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
=> new(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
return key is TKey tkey
|
||||
&& ContainsKey(tkey);
|
||||
}
|
||||
|
||||
void IDictionary.Add(object key, object value) => throw new NotImplementedException();
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.DICT_ENTRY);
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
if (key is TKey tkey)
|
||||
Remove(tkey);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index) => throw new NotImplementedException();
|
||||
|
||||
internal struct Entry
|
||||
{
|
||||
public uint HashCode;
|
||||
public TKey Key;
|
||||
public EntryReference Next;
|
||||
public TValue Value;
|
||||
|
||||
internal Entry(uint hashCode, TKey key, TValue value)
|
||||
{
|
||||
HashCode = hashCode;
|
||||
Key = key;
|
||||
Value = value;
|
||||
Next = EntryReference.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal class EntryReference
|
||||
{
|
||||
public static readonly EntryReference Empty = new();
|
||||
|
||||
public Entry Entry;
|
||||
|
||||
public EntryReference(uint hashCode, TKey key, TValue value) => Entry = new(hashCode, key, value);
|
||||
|
||||
public EntryReference(Entry entry) => Entry = entry;
|
||||
|
||||
private EntryReference() { }
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator
|
||||
{
|
||||
private readonly FishTable<TKey, TValue> _dictionary;
|
||||
private readonly int _version;
|
||||
private int _index;
|
||||
private EntryReference _next;
|
||||
private KeyValuePair<TKey, TValue> _current;
|
||||
private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
|
||||
|
||||
internal const int DICT_ENTRY = 1;
|
||||
internal const int KEY_VALUE_PAIR = 2;
|
||||
|
||||
internal Enumerator(FishTable<TKey, TValue> dictionary, int getEnumeratorRetType)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
_version = dictionary._version;
|
||||
_index = 0;
|
||||
_getEnumeratorRetType = getEnumeratorRetType;
|
||||
_current = default;
|
||||
_next = EntryReference.Empty;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_version != _dictionary._version)
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
||||
|
||||
if (_next != EntryReference.Empty)
|
||||
{
|
||||
_current = new(_next.Entry.Key, _next.Entry.Value);
|
||||
_next = _next.Entry.Next;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
|
||||
// dictionary.count+1 could be negative if dictionary.count is int.MaxValue
|
||||
while ((uint)_index < (uint)_dictionary._entries.Length)
|
||||
{
|
||||
ref var entry = ref _dictionary._entries[_index++];
|
||||
|
||||
if (entry.Next != null)
|
||||
{
|
||||
_current = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
_next = entry.Next;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_index = _dictionary._entries.Length + 1;
|
||||
_current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeyValuePair<TKey, TValue> Current => _current;
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
object? IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _getEnumeratorRetType == DICT_ENTRY
|
||||
? new DictionaryEntry(_current.Key, _current.Value)
|
||||
: new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
if (_version != _dictionary._version)
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
||||
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
DictionaryEntry IDictionaryEnumerator.Entry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return new DictionaryEntry(_current.Key, _current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
object? IDictionaryEnumerator.Key
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _current.Key;
|
||||
}
|
||||
}
|
||||
|
||||
object? IDictionaryEnumerator.Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _current.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThrowHelper
|
||||
{
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
|
||||
=> throw new InvalidOperationException("Collection was modified; enumeration operation may not execute.");
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
|
||||
=> throw new InvalidOperationException("Enumeration has either not started or has already finished.");
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static V ThrowKeyNotFoundException<T, V>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
return default;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException(object key)
|
||||
=> throw new KeyNotFoundException(
|
||||
string.Format("The given key '{0}' was not present in the dictionary.", key));
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowWrongValueTypeArgumentException(object value)
|
||||
=> throw new ArgumentException(
|
||||
string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.",
|
||||
value, typeof(TValue)),
|
||||
nameof(value));
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowWrongKeyTypeArgumentException(object? key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
throw new ArgumentException(
|
||||
string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.",
|
||||
key, typeof(TKey)),
|
||||
nameof(key));
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowAddingDuplicateWithKeyArgumentException((object)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowAddingDuplicateWithKeyArgumentException(object key)
|
||||
=> throw new ArgumentException(
|
||||
string.Format("An item with the same key has already been added. Key: {0}", key));
|
||||
}
|
||||
}
|
||||
public static class Extensions
|
||||
{
|
||||
public static ref TValue TryGetReferenceUnsafe<TKey, TValue>(this FishTable<TKey, TValue> fishTable, ref TKey key)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(ref key);
|
||||
ref var entry = ref fishTable._entries[FishTable<TKey, TValue>.FastModulo(keyCode, (uint)fishTable._entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals(ref key))
|
||||
{
|
||||
if (entry.Next != FishTable<TKey, TValue>.EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<TValue>()!;
|
||||
}
|
||||
|
||||
return ref entry.Next == null
|
||||
? ref Unsafe.NullRef<TValue>()
|
||||
: ref entry.Value;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS8766, CS8767
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
/// <summary>
|
||||
/// Log Functions that only do something when compiling in Debug Mode
|
||||
/// </summary>
|
||||
public static class DebugLog
|
||||
{
|
||||
[Conditional("DEBUG")]
|
||||
public static void Message(string text) => Log.Message(text);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Warning(string text) => Log.Warning(text);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Error(string text) => Log.Error(text);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#if V1_3
|
||||
//mostly integrated into vanilla with 1.4
|
||||
|
||||
using System.Linq;
|
||||
using AllDesignationsOnCache = PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.Thing, PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.Thing>>;
|
||||
using DesignationsOfDefCache = PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.DesignationDef, PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.DesignationDef>>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class DesignationManagerCaching : ClassWithFishPatches
|
||||
{
|
||||
public class SpawnedDesignationsOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.SpawnedDesignationsOfDef(null);
|
||||
public static bool Prefix(DesignationManager __instance, DesignationDef def, ref IEnumerable<Designation> __result, out bool __state)
|
||||
{
|
||||
if (def.GetType() != typeof(DesignationDef)) // different types have different indexing. Can't use those
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DesignationsOfDefCache.TryGetValue(new(__instance, def), out var value))
|
||||
return __state = true;
|
||||
|
||||
__result = value.designations;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(DesignationManager __instance, DesignationDef def, bool __state, IEnumerable<Designation> __result)
|
||||
{
|
||||
if (__state)
|
||||
RefreshDesignationCache(DesignationsOfDefCache.Get, __instance, def, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RefreshDesignationCache<T>(/*Cache.ByIndex<DesignationManager, T, DesignationCache<T>>*/
|
||||
Dictionary<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>> dict, DesignationManager instance, T key, IEnumerable<Designation> result)
|
||||
where T : notnull
|
||||
{
|
||||
if (dict.TryGetValue(new(instance, key), out var cache))
|
||||
cache.UpdateList(result);
|
||||
else
|
||||
cache.Initialize(result, instance);
|
||||
|
||||
dict[new(instance, key)] = cache;
|
||||
}
|
||||
|
||||
public class AnySpawnedDesignationOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.AnySpawnedDesignationOfDef(null);
|
||||
public static bool AnySpawnedDesignationOfDef(DesignationManager __instance, DesignationDef def) => __instance.SpawnedDesignationsOfDef(def).Any();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(AnySpawnedDesignationOfDef);
|
||||
}
|
||||
|
||||
public class AllDesignationsOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.AllDesignationsOn(null);
|
||||
public static bool Prefix(DesignationManager __instance, Thing t, ref IEnumerable<Designation> __result, out bool __state)
|
||||
{
|
||||
if (!AllDesignationsOnCache.TryGetValue(new(__instance, t), out var value))
|
||||
return __state = true;
|
||||
|
||||
__result = value.designations;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(DesignationManager __instance, Thing t, bool __state, IEnumerable<Designation> __result)
|
||||
{
|
||||
if (__state)
|
||||
RefreshDesignationCache(AllDesignationsOnCache.Get, __instance, t, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class DesignationOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.DesignationOn(null);
|
||||
public static Designation DesignationOn(DesignationManager __instance, Thing t) => __instance.AllDesignationsOn(t).FirstOrDefault();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(DesignationOn);
|
||||
}
|
||||
|
||||
public class DesignationOn_ByDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.DesignationOn(null, null);
|
||||
public static Designation? DesignationOn(DesignationManager __instance, Thing t, DesignationDef def) => FirstDesignationOfDef(__instance.AllDesignationsOn(t), def);
|
||||
public static Designation? FirstDesignationOfDef(IEnumerable<Designation> designations, DesignationDef def)
|
||||
{
|
||||
if (designations is not List<Designation> designationList)
|
||||
return Fallback(designations, def);
|
||||
|
||||
for (var i = 0; i < designationList.Count; i++)
|
||||
{
|
||||
var designation = designationList[i];
|
||||
if (designation.def == def)
|
||||
return designation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static Designation? Fallback(IEnumerable<Designation> designations, DesignationDef def)
|
||||
{
|
||||
foreach (var designation in designations)
|
||||
{
|
||||
if (designation.def == def)
|
||||
return designation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(DesignationOn);
|
||||
}
|
||||
|
||||
public class HasMapDesignationOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.HasMapDesignationOn(null);
|
||||
public static bool HasMapDesignationOn(DesignationManager __instance, Thing t) => __instance.AllDesignationsOn(t).Any();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(HasMapDesignationOn);
|
||||
}
|
||||
|
||||
public struct DesignationCache<T> : Cache.IIsRefreshable<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>>
|
||||
where T : notnull
|
||||
{
|
||||
public IEnumerable<Designation> designations;
|
||||
private int _nextRefreshTick;
|
||||
private int _managerListState;
|
||||
private DesignationManager _manager;
|
||||
|
||||
public void Initialize(IEnumerable<Designation> designations, DesignationManager manager)
|
||||
{
|
||||
this.designations = new List<Designation>(designations);
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_manager = manager;
|
||||
_managerListState = manager.allDesignations.Version();
|
||||
}
|
||||
public void UpdateList(IEnumerable<Designation> designations)
|
||||
{
|
||||
var cachedList = (List<Designation>)this.designations;
|
||||
cachedList.Clear();
|
||||
cachedList.AddRange(designations);
|
||||
ShouldRefreshNow = false;
|
||||
_managerListState = _manager.allDesignations.Version();
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame || _managerListState != _manager.allDesignations.Version();
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public DesignationCache<T> SetNewValue(Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>> key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*using System.Linq;
|
||||
|
||||
namespace PerformanceFish;
|
||||
[StaticConstructorOnStartup]
|
||||
public static class FishDefOfOrNull
|
||||
{
|
||||
public static RecipeDef VFEA_MendWeapon { get; }
|
||||
public static RecipeDef VFEA_MendArmor { get; }
|
||||
public static RecipeDef VFEA_MendApparel { get; }
|
||||
|
||||
static FishDefOfOrNull()
|
||||
{
|
||||
foreach (var property in typeof(FishDefOfOrNull).GetProperties())
|
||||
{
|
||||
typeof(FishDefOfOrNull).GetFields(BindingFlags.Static | BindingFlags.NonPublic).First(f => f.Name.Contains(property.Name)).SetValue(null,
|
||||
typeof(FishDefOfOrNull).GetMethod(nameof(DefOf), BindingFlags.Static | BindingFlags.NonPublic)
|
||||
.MakeGenericMethod(property.GetMethod.ReturnType).Invoke(null, new[] { property.Name }));
|
||||
}
|
||||
}
|
||||
private static T DefOf<T>(string name) where T : Def => DefDatabase<T>.GetNamedSilentFail(name);
|
||||
}*/
|
||||
@@ -0,0 +1,399 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasDescription
|
||||
{
|
||||
public virtual bool ShouldBenchmark => false;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
if (value)
|
||||
{
|
||||
if (ShouldBePatched)
|
||||
TryPatch();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shouldBePatched = ShouldBePatched;
|
||||
TryUnpatch();
|
||||
ShouldBePatched = shouldBePatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _enabled = true;
|
||||
public virtual bool DefaultState => true;
|
||||
public virtual int PrefixMethodPriority => TryGetPriority(PrefixMethodInfo);
|
||||
public virtual int PostfixMethodPriority => TryGetPriority(PostfixMethodInfo);
|
||||
public virtual int TranspilerMethodPriority => TryGetPriority(TranspilerMethodInfo);
|
||||
public virtual int FinalizerMethodPriority => TryGetPriority(FinalizerMethodInfo);
|
||||
public MethodInfo? HarmonyMethodInfo => HarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> HarmonyMethodInfos { get; } = new();
|
||||
public MethodInfo? BenchmarkHarmonyMethodInfo => BenchmarkHarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = new();
|
||||
private bool Patched { get; set; }
|
||||
private bool ShouldBePatched { get; set; }
|
||||
|
||||
public virtual string? Name => null;
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual Delegate? TargetMethodGroup => null;
|
||||
public virtual IEnumerable<Delegate>? TargetMethodGroups => null;
|
||||
public virtual Expression<Action>? TargetMethod => null;
|
||||
public virtual IEnumerable<Expression<Action>>? TargetMethods => null;
|
||||
public virtual MethodBase TargetMethodInfo
|
||||
=> TargetMethod != null
|
||||
? SymbolExtensions.GetMethodInfo(TargetMethod)
|
||||
: TargetMethodGroup?.Method
|
||||
?? throw new MissingMethodException(GetType().ToString());
|
||||
|
||||
public virtual IEnumerable<MethodBase> TargetMethodInfos
|
||||
=> _targetMethodInfos
|
||||
??= (TargetMethods?.Select(m
|
||||
=> SymbolExtensions.GetMethodInfo(m))
|
||||
?? TargetMethodGroups?.Select(m => m.Method)
|
||||
?? (TargetMethodInfo != null
|
||||
? new MethodBase[] { TargetMethodInfo }.AsEnumerable()
|
||||
: throw new MissingMethodException(GetType().ToString()))).ToArray();
|
||||
|
||||
private MethodBase[]? _targetMethodInfos;
|
||||
|
||||
public virtual MethodInfo ReversePatchMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "REVERSEPATCH" || (m.HasAttribute<HarmonyReversePatch>() && !m.HasAttribute<HarmonyTranspiler>()));
|
||||
public virtual MethodInfo ReversePatchTranspilerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "REVERSEPATCHTRANSPILER" || (m.HasAttribute<HarmonyTranspiler>() && m.HasAttribute<HarmonyReversePatch>()));
|
||||
public virtual MethodInfo PrefixMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "PREFIX" || m.HasAttribute<HarmonyPrefix>());
|
||||
public virtual MethodInfo PostfixMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "POSTFIX" || m.HasAttribute<HarmonyPostfix>());
|
||||
public virtual MethodInfo TranspilerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "TRANSPILER" || (m.HasAttribute<HarmonyTranspiler>() && !m.HasAttribute<HarmonyReversePatch>()));
|
||||
public virtual MethodInfo FinalizerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "FINALIZER" || m.HasAttribute<HarmonyFinalizer>());
|
||||
|
||||
internal bool IsBenchmarking => ShouldBenchmark && !FinishedBenchmarking;
|
||||
internal bool FinishedBenchmarking = false;
|
||||
|
||||
protected FishPatch() => _enabled = DefaultState;
|
||||
|
||||
public virtual MethodInfo? TryPatch()
|
||||
{
|
||||
if (!TargetMethodInfos?.Any() ?? true)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but it lacks a target method for patching.");
|
||||
return null;
|
||||
}
|
||||
if (PrefixMethodInfo is null && PostfixMethodInfo is null && TranspilerMethodInfo is null && FinalizerMethodInfo is null && ReversePatchMethodInfo is null)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but there are none. This is likely not intended.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ShouldBePatched = true;
|
||||
if (Enabled && !Patched)
|
||||
ApplyPatch();
|
||||
|
||||
return HarmonyMethodInfo;
|
||||
}
|
||||
|
||||
protected virtual void ApplyPatch()
|
||||
{
|
||||
Patched = true;
|
||||
foreach (var method in TargetMethodInfos!)
|
||||
{
|
||||
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
|
||||
|
||||
try
|
||||
{
|
||||
if (IsBenchmarking)
|
||||
{
|
||||
Patched = false;
|
||||
|
||||
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
||||
{
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(moveNext,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(method,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ReversePatchMethodInfo != null)
|
||||
HarmonyMethodInfos.Add(Harmony.ReversePatch(method, new(ReversePatchMethodInfo), ReversePatchTranspilerMethodInfo));
|
||||
|
||||
if (PrefixMethodInfo != null || PostfixMethodInfo != null || TranspilerMethodInfo != null || FinalizerMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(method,
|
||||
prefix: PrefixMethodInfo != null ? new(PrefixMethodInfo, PrefixMethodPriority) : null,
|
||||
postfix: PostfixMethodInfo != null ? new(PostfixMethodInfo, PostfixMethodPriority) : null,
|
||||
transpiler: TranspilerMethodInfo != null ? new(TranspilerMethodInfo, TranspilerMethodPriority) : null,
|
||||
finalizer: FinalizerMethodInfo != null ? new(FinalizerMethodInfo, FinalizerMethodPriority) : null));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to patch {method.FullDescription()} with {PrefixMethodInfo.FullDescription()}" +
|
||||
$", {PostfixMethodInfo.FullDescription()}, {TranspilerMethodInfo.FullDescription()}, {FinalizerMethodInfo.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void TryUnpatch()
|
||||
{
|
||||
ShouldBePatched = false;
|
||||
if (/*!Enabled &&*/ Patched)
|
||||
ApplyUnpatching();
|
||||
}
|
||||
|
||||
protected virtual void ApplyUnpatching()
|
||||
{
|
||||
Patched = false;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
if (PrefixMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, PrefixMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {PrefixMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (PostfixMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, PostfixMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {PostfixMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (TranspilerMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, TranspilerMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {TranspilerMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (FinalizerMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, FinalizerMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {FinalizerMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int TryGetPriority(MethodInfo info) => info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
||||
|
||||
public virtual void ExposeData()
|
||||
=> Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
}
|
||||
|
||||
internal static class Benchmarking
|
||||
{
|
||||
internal static int SwitchFrame = int.MaxValue;
|
||||
internal static int EndFrame = int.MaxValue;
|
||||
|
||||
public static void Prefix(MethodBase __originalMethod, out Results __state)
|
||||
{
|
||||
if (!Cache.ByReference<MethodBase, Results>.Get.TryGetValue(__originalMethod, out __state))
|
||||
Cache.ByReference<MethodBase, Results>.Get[__originalMethod] = __state = new(__originalMethod);
|
||||
|
||||
__state.Stopwatch.Start();
|
||||
}
|
||||
|
||||
public static void Postfix(Results __state)
|
||||
{
|
||||
__state.Stopwatch.Stop();
|
||||
__state.Count++;
|
||||
|
||||
//Log.Message($"SwitchFrame is {SwitchFrame}, EndFrame is {EndFrame}, CurrentFrame is {Time.frameCount}");
|
||||
|
||||
//if (__state.Stopwatch.ElapsedMilliseconds > 100 || __state.Count > 100000)
|
||||
if (__state.Switched ? GenTicks.TicksGame > EndFrame : GenTicks.TicksGame > SwitchFrame)
|
||||
PrintResultsAndStop(__state);
|
||||
}
|
||||
|
||||
public static void PrintResultsAndStop(Results results)
|
||||
{
|
||||
var ticks = results.Stopwatch.ElapsedTicks;
|
||||
Log.Message($"{results.Method.FullDescription()} took {ticks} ticks for {results.Count} calls. This equals {ticks * 1000L / results.Count} ticks per 1000 calls on average.");
|
||||
|
||||
results.Count = 0;
|
||||
results.Switched = true;
|
||||
results.Stopwatch.Reset();
|
||||
|
||||
var fishPatch = PerformanceFishMod.AllPatchClasses
|
||||
.SelectMany(patchClass
|
||||
=> patchClass.Patches)
|
||||
.First(patch
|
||||
=> patch.TargetMethodInfos.Contains(results.Method));
|
||||
|
||||
if (fishPatch.FinishedBenchmarking)
|
||||
{
|
||||
foreach (var target in fishPatch.TargetMethodInfos)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(target) is { } moveNext)
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Postfix));
|
||||
}
|
||||
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Postfix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fishPatch.FinishedBenchmarking = true;
|
||||
fishPatch.TryPatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class Results
|
||||
{
|
||||
public int Count;
|
||||
public Stopwatch Stopwatch;
|
||||
public MethodBase Method;
|
||||
public bool Switched;
|
||||
|
||||
public Results(MethodBase method)
|
||||
{
|
||||
Stopwatch = new();
|
||||
Method = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class FirstPriorityFishPatch : FishPatch
|
||||
{
|
||||
public override int PrefixMethodPriority => Priority.First;
|
||||
public override int PostfixMethodPriority => Priority.First;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
public override int FinalizerMethodPriority => Priority.First;
|
||||
}
|
||||
|
||||
public static class FishPatchExtensions
|
||||
{
|
||||
public static void PatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryPatch();
|
||||
}
|
||||
public static void UnpatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryUnpatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPatch> All => _all;
|
||||
public T Get<T>() where T : FishPatch => (T)All[typeof(T)];
|
||||
public void Add(FishPatch patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll() => All.Values.PatchAll();
|
||||
public void UnpatchAll() => All.Values.UnpatchAll();
|
||||
public FishPatch this[Type type] => All[type];
|
||||
|
||||
public FishPatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName); // directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPatch.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(IHasFishPatch[] patches, Type type)
|
||||
{
|
||||
var dupeClasses = patches
|
||||
.Where(patchClass
|
||||
=> patchClass.GetType() != _type
|
||||
&& patchClass.Patches.All.ContainsKey(type));
|
||||
|
||||
if (!dupeClasses.Any())
|
||||
return;
|
||||
|
||||
foreach (var patchClass in dupeClasses)
|
||||
{
|
||||
patchClass.Patches[type].Enabled = false;
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {patchClass.GetType().FullName}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPatch> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private ConcurrentDictionary<Type, FishPatch> _all = new();
|
||||
private Type _type;
|
||||
}
|
||||
|
||||
public interface IHasFishPatch
|
||||
{
|
||||
public FishPatchHolder Patches { get; }
|
||||
public bool RequiresLoadedGameForPatching { get; }
|
||||
}
|
||||
|
||||
public interface IHasDescription
|
||||
{
|
||||
public string? Description { get; }
|
||||
}
|
||||
|
||||
public abstract class ClassWithFishPatches : SingletonFactory<ClassWithFishPatches>, IHasFishPatch
|
||||
{
|
||||
public virtual FishPatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class FishSettings : ModSettings
|
||||
{
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
Scribe_Values.Look(ref _threadingEnabled, "ThreadingEnabled", false);
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
patchClass.Patches.Scribe();
|
||||
}
|
||||
|
||||
public static void DoSettingsWindowContents(Rect inRect)
|
||||
{
|
||||
Widgets.BeginScrollView(inRect, ref _scrollPosition, _scrollRect);
|
||||
var ls = new Listing_Standard();
|
||||
ls.Begin(_scrollRect);
|
||||
|
||||
var checkModEnabled = ModEnabled;
|
||||
ls.CheckboxLabeled("Toggle all patches", ref checkModEnabled, "This enables or disables all patches of the mod with one click. For testing purposes");
|
||||
if (checkModEnabled != ModEnabled)
|
||||
{
|
||||
ModEnabled = checkModEnabled;
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
foreach (var patch in patchClass.Patches)
|
||||
patch.Enabled = checkModEnabled;
|
||||
|
||||
if (checkModEnabled && (!patchClass.RequiresLoadedGameForPatching || Current.ProgramState == ProgramState.Playing))
|
||||
patchClass.Patches.PatchAll();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
try
|
||||
{
|
||||
ls.Label(patchClass.GetType().Name,
|
||||
tooltip: patchClass is IHasDescription classWithDescription
|
||||
? classWithDescription.Description
|
||||
: null);
|
||||
|
||||
ls.GapLine(2f);
|
||||
foreach (var patch in patchClass.Patches)
|
||||
{
|
||||
if (ShouldSkipForScrollView(inRect.height, Text.LineHeight, ls.curY, _scrollPosition.y))
|
||||
{
|
||||
ls.curY += Text.LineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
var check = patch.Enabled;
|
||||
if (patch.Description == null)
|
||||
Widgets.DrawHighlightIfMouseover(_scrollRect with { y = ls.curY, height = Text.LineHeight });
|
||||
var label = patch.Name ?? patch.GetType().Name;
|
||||
if (label.EndsWith("_Patch"))
|
||||
label = label.Remove(label.Length - 6);
|
||||
ls.CheckboxLabeled(label, ref check, patch.Description);
|
||||
if (check != patch.Enabled)
|
||||
patch.Enabled = check;
|
||||
}
|
||||
ls.Gap();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
//ls.Gap();
|
||||
//var threadingEnabled = ThreadingEnabled;
|
||||
//ls.CheckboxLabeled("Allow threading", ref threadingEnabled, "Currently affects the ListerHaulables tick, ingredient lookups in WorkGiver_DoBill and refreshing of the shared altar thoughtworker cache. " +
|
||||
// "Experimental and disabled by default.");
|
||||
//ThreadingEnabled = threadingEnabled;
|
||||
|
||||
ls.Gap();
|
||||
if (ls.ButtonText("Clear Cache"))
|
||||
Cache.Utility.Clear();
|
||||
|
||||
ls.End();
|
||||
|
||||
Widgets.EndScrollView();
|
||||
_scrollRect = _scrollRect with { height = ls.curY + 50f, width = inRect.width - GUI.skin.verticalScrollbar.fixedWidth - 5f };
|
||||
}
|
||||
|
||||
public static bool ShouldSkipForScrollView(float scrollViewSize, float entrySize, float entryPosition, float scrollPosition)
|
||||
=> entryPosition + entrySize < scrollPosition || entryPosition > scrollPosition + scrollViewSize;
|
||||
|
||||
public static bool ModEnabled { get => _modEnabled; set => _modEnabled = value; }
|
||||
private static bool _modEnabled = true;
|
||||
public static bool ThreadingEnabled
|
||||
{
|
||||
get => false; //_threadingEnabled;
|
||||
set
|
||||
{
|
||||
if (value == _threadingEnabled)
|
||||
return;
|
||||
|
||||
ThreadingPatches.ForEach(p => p.Enabled = value);
|
||||
_threadingEnabled = value;
|
||||
}
|
||||
}
|
||||
private static bool _threadingEnabled = false;
|
||||
public static List<FishPatch> ThreadingPatches { get; } = new();
|
||||
private static Rect _scrollRect = new(0f, 0f, 500f, 9001f);
|
||||
private static Vector2 _scrollPosition;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish.FunctionPointers;
|
||||
|
||||
public static class IndexGetter<T> where T : notnull
|
||||
{
|
||||
public static readonly unsafe delegate*<T, int> Default = IndexGetter.GetDefaultIndexFunctionPointer<T>();
|
||||
}
|
||||
|
||||
public static class IndexGetter
|
||||
{
|
||||
public static class Methods
|
||||
{
|
||||
public static int HediffSet(HediffSet set) => set.pawn.thingIDNumber;
|
||||
public static int HediffDef(HediffDef def) => def.index;
|
||||
public static int Bill(Bill bill) => bill.loadID;
|
||||
public static int Pawn(Pawn pawn) => pawn.thingIDNumber;
|
||||
public static int Thing(Thing thing) => thing.thingIDNumber;
|
||||
public static int CompAssignableToPawn_Bed(CompAssignableToPawn_Bed comp) => comp.parent.thingIDNumber;
|
||||
public static int Map(Map map) => map.uniqueID;
|
||||
public static int DesignationManager(DesignationManager manager) => manager.map.uniqueID;
|
||||
public static int DesignationDef(DesignationDef def) => def.index;
|
||||
public static int Room(Room room) => room.ID;
|
||||
public static int Ideo(Ideo ideo) => ideo.id;
|
||||
public static int RecipeDef(RecipeDef def) => def.index;
|
||||
}
|
||||
|
||||
internal static Cache.IndexGetter<T> GetDefaultIndexGetter<T>() where T : notnull
|
||||
=> _indexFuncs.TryGetValue(typeof(T), out var value)
|
||||
? (Cache.IndexGetter<T>)value
|
||||
: Fallback<T>();
|
||||
|
||||
internal static unsafe delegate*<T, int> GetDefaultIndexFunctionPointer<T>() where T : notnull
|
||||
=> (delegate*<T, int>)(GetGetterMethods().FirstOrDefault(m => m.GetParameters()[0].ParameterType == typeof(T))
|
||||
?? Fallback<T>().Method).MethodHandle.GetFunctionPointer();
|
||||
|
||||
private static IEnumerable<MethodInfo> GetGetterMethods()
|
||||
=> typeof(Methods)
|
||||
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
|
||||
.Where(m
|
||||
=> m.ReturnType == typeof(int)
|
||||
&& m.GetParameters().Length == 1);
|
||||
|
||||
private static Cache.IndexGetter<T> Fallback<T>() where T : notnull
|
||||
{
|
||||
var isDef = typeof(Def).IsAssignableFrom(typeof(T));
|
||||
var isThing = false; //typeof(Thing).IsAssignableFrom(typeof(T)) && !typeof(Mote).IsAssignableFrom(typeof(T)); // generally always safe in vanilla, but logging mods won't hurt either
|
||||
|
||||
var message = $"Performance Fish failed to find type {typeof(T).FullDescription()} in indexFuncs dictionary. Using fallback instead";
|
||||
|
||||
if (isDef)
|
||||
Log.Error($"{message} to prevent further issues, but this likely won't work correctly.");
|
||||
else if (!isThing)
|
||||
Log.Warning($"{message}.");
|
||||
|
||||
var type = typeof(T).BaseType;
|
||||
Delegate func;
|
||||
while (!_indexFuncs.TryGetValue(type, out func))
|
||||
type = type.BaseType ?? throw new Exception($"Performance Fish failed to find a fallback method to handle type {typeof(T).FullDescription()} from mod {typeof(T).Assembly.GetName().Name}");
|
||||
|
||||
if (!isThing)
|
||||
Log.Message($"Found fallback of type {type.FullDescription()}.{(!isDef ? " This likely means we can safely continue from here." : "")}");
|
||||
|
||||
return (Cache.IndexGetter<T>)func;
|
||||
}
|
||||
|
||||
private static Dictionary<Type, Delegate> _indexFuncs
|
||||
= GetGetterMethods()
|
||||
.ToDictionary(
|
||||
m => m.GetParameters()[0].ParameterType,
|
||||
m => m.CreateDelegate(typeof(Cache.IndexGetter<>).MakeGenericType(m.GetParameters()[0].ParameterType)));
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class GenLocalDateCaching : ClassWithFishPatches
|
||||
{
|
||||
public class DayTickByThing_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state optimization, but yields accurate results instead of a placeholder value to avoid issues.";
|
||||
public override Delegate? TargetMethodGroup => (Func<Thing, int>)GenLocalDate.DayTick;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes, MethodBase method, ILGenerator generator)
|
||||
//=> Reflection.GetCodeInstructions(Replacement);
|
||||
{
|
||||
var labelToStartOfMethod = generator.DefineLabel();
|
||||
codes.First().labels.Add(labelToStartOfMethod);
|
||||
|
||||
yield return FishTranspiler.Argument(method, "thing");
|
||||
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
|
||||
yield return FishTranspiler.Constant(0);
|
||||
yield return FishTranspiler.IfLessThan(labelToStartOfMethod);
|
||||
|
||||
yield return FishTranspiler.Field(typeof(Current), nameof(Current.gameInt));
|
||||
yield return FishTranspiler.Field(typeof(Game), nameof(Game.maps));
|
||||
yield return FishTranspiler.Argument(method, "thing");
|
||||
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
|
||||
yield return FishTranspiler.Call(typeof(List<Map>), "get_Item");
|
||||
yield return FishTranspiler.Call<Func<Map, int>>(GenLocalDate.DayTick);
|
||||
yield return FishTranspiler.Return;
|
||||
|
||||
foreach (var code in codes)
|
||||
yield return code;
|
||||
|
||||
}
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//public static int Replacement(Thing thing)
|
||||
// => thing.mapIndexOrState < 0 ? GenDate.DayTick(GenTicks.TicksAbs, GenLocalDate.LongitudeForDate(thing))
|
||||
// : GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//public static bool Prefix(Thing thing, ref int __result)
|
||||
//{
|
||||
// if (thing.mapIndexOrState < 0)
|
||||
// return true;
|
||||
|
||||
// __result = GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
|
||||
public class DayTickByMap_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state optimization, but yields accurate results instead of a placeholder value to avoid issues.";
|
||||
public override Delegate? TargetMethodGroup => (Func<Map, int>)GenLocalDate.DayTick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Map map, ref int __result, out bool __state)
|
||||
{
|
||||
if (map != SavedMap)
|
||||
{
|
||||
if (SavedMap is null && Find.Maps?.Count > 0 && Find.Maps[0] == map)
|
||||
SavedMap = map;
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
if (SavedTick != GenTicks.TicksGame)
|
||||
return __state = true;
|
||||
|
||||
__result = DayTick;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Map map, int __result, bool __state)
|
||||
{
|
||||
if (!__state || SavedMap != map)
|
||||
return;
|
||||
|
||||
SavedTick = GenTicks.TicksGame;
|
||||
DayTick = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map? SavedMap;
|
||||
public static int SavedTick;
|
||||
public static int DayTick;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class HediffComp_PsychicHarmonizer_Bugfix : ClassWithFishPatches
|
||||
{
|
||||
public class CompPostTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Fixes a bug in the psychic harmonizer tick method that was causing it to recalculate results on 149 out of 150 ticks instead of once every 150 ticks. Large impact.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffComp_PsychicHarmonizer), nameof(HediffComp_PsychicHarmonizer.CompPostTick));
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
var pawn_int_IsHashIntervalTick = FishTranspiler.Call<Func<Thing, int, bool>>(Gen.IsHashIntervalTick);
|
||||
var success = false;
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (i + 2 < codes.Count && codes[i].opcode == Ldc_I4 && codes[i + 1] == pawn_int_IsHashIntervalTick && codes[i + 2].opcode == Brfalse_S)
|
||||
{
|
||||
//codes[i].operand = 128;
|
||||
yield return codes[i];
|
||||
yield return codes[i + 1];
|
||||
codes[i + 2].opcode = Brtrue_S;
|
||||
yield return codes[i + 2];
|
||||
success = true;
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
Log.Warning("Performance Fish failed to insert its Psychic Harmonizer Tick bugfix. This is probably harmless and likely just means someone else fixed it already");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using PerformanceFish.Collections;
|
||||
using HediffCache = PerformanceFish.Cache.ByIntRefreshable<Verse.HediffSet, Verse.HediffDef, PerformanceFish.HediffSetCaching.HediffCacheValue>;
|
||||
using VisibleHediffCache = PerformanceFish.Cache.ByIntRefreshable<Verse.HediffSet, Verse.HediffDef, PerformanceFish.HediffSetCaching.VisibleHediffCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class HediffSetCaching : ClassWithFishPatches
|
||||
{
|
||||
public class HasHediff_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the HediffSet.HasHediff method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs.";
|
||||
public override Expression<Action> TargetMethod => () => default(HediffSet)!.HasHediff(null, default);
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.GetCodeInstructions(Replacement);
|
||||
public static bool Replacement(HediffSet __instance, HediffDef def, bool mustBeVisible) => __instance.GetFirstHediffOfDef(def, mustBeVisible) != null;
|
||||
}
|
||||
|
||||
public class GetFirstHediffOfDef_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the HediffSet.GetFirstHediffOfDef method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs.";
|
||||
public override Expression<Action> TargetMethod => () => default(HediffSet)!.GetFirstHediffOfDef(null, default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(HediffSet __instance, HediffDef def, bool mustBeVisible, ref Hediff? __result, out bool __state)
|
||||
{
|
||||
if (__instance.hediffs.Count < 5 || def is null || def.GetType() != typeof(HediffDef)) // different types have different indexing. Can't use those
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new HediffCache() { First = __instance.pawn.thingIDNumber, Second = def.index };
|
||||
ref var cache = ref mustBeVisible ? ref Unsafe.As<VisibleHediffCacheValue, HediffCacheValue>(ref VisibleHediffCache.Get.TryGetReferenceUnsafe(ref Unsafe.As<HediffCache, VisibleHediffCache>(ref key)))
|
||||
: ref HediffCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Hediff;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(HediffSet __instance, HediffDef def, bool mustBeVisible, Hediff? __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
if (mustBeVisible)
|
||||
VisibleHediffCache.Get[new(__instance, def)] = new(__instance, __result);
|
||||
else
|
||||
HediffCache.Get[new(__instance, def)] = new(__instance, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffSet_DirtyCache_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Patched to trigger Psylink cache clearing for the psychic entropy optimization.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache));
|
||||
public static void Postfix(HediffSet __instance)
|
||||
{
|
||||
if (__instance.pawn.psychicEntropy is { } entropy)
|
||||
entropy.psylinkCachedForTick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public struct HediffCacheValue : Cache.IIsRefreshable<HediffCache, HediffCacheValue>
|
||||
{
|
||||
private int _nextRefreshTick;
|
||||
private int _version;
|
||||
private HediffSet _set;
|
||||
public Hediff? Hediff;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public HediffCacheValue(HediffSet set, Hediff? hediff)
|
||||
{
|
||||
_set = set;
|
||||
Hediff = hediff;
|
||||
_version = set.hediffs._version;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.ticksGameInt || _version != _set.hediffs._version;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public HediffCacheValue SetNewValue(HediffCache key) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public struct VisibleHediffCacheValue : Cache.IIsRefreshable<VisibleHediffCache, VisibleHediffCacheValue>
|
||||
{
|
||||
public HediffCacheValue Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public VisibleHediffCacheValue(HediffSet set, Hediff? hediff) => Value = new(set, hediff);
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Value.ShouldRefreshNow;
|
||||
set => Value.ShouldRefreshNow = value;
|
||||
}
|
||||
|
||||
public VisibleHediffCacheValue SetNewValue(VisibleHediffCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using PerformanceFish.Cache;
|
||||
using StyleDominanceCache = PerformanceFish.Cache.ByIntRefreshable<Verse.Thing, RimWorld.Ideo, PerformanceFish.IdeoUtilityCaching.StyleDominanceCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class IdeoUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GetStyleDominance_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Caches IdeoUtility.GetStyleDominance to only update about once every 600 ticks or so. Only active when not using Performance Optimizer, as that mod includes a similar version too.";
|
||||
public override bool Enabled => base.Enabled && !ModsConfig.IsActive("Taranchuk.PerformanceOptimizer");
|
||||
public override Delegate? TargetMethodGroup => IdeoUtility.GetStyleDominance;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Thing t, Ideo ideo, ref float __result, out bool __state)
|
||||
{
|
||||
if (t is null || ideo is null || t.thingIDNumber < 0)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new StyleDominanceCache(t.thingIDNumber, ideo.id);
|
||||
ref var cache = ref StyleDominanceCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Value;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Thing t, Ideo ideo, bool __state, float __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
StyleDominanceCache.Get[new(t, ideo)] = new(__result);
|
||||
}
|
||||
}
|
||||
|
||||
public struct StyleDominanceCacheValue : IIsRefreshable<StyleDominanceCache, StyleDominanceCacheValue>
|
||||
{
|
||||
public float Value;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public StyleDominanceCacheValue(float value)
|
||||
{
|
||||
Value = value;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 512 + Math.Abs(Rand.Int % 256); // Pawn_StyleObserverTracker.StyleObservableTick runs every 900 ticks
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 512 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
|
||||
StyleDominanceCacheValue IIsRefreshable<StyleDominanceCache, StyleDominanceCacheValue>.SetNewValue(StyleDominanceCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*using System.Threading.Tasks;
|
||||
using Verse.AI;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class GenClosestPatches : ClassWithFishPatches
|
||||
{*/
|
||||
#if disabledForNow
|
||||
public class ClosestThing_Global_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Threading for job scanning. Disabled and unstable.";
|
||||
public override bool DefaultState => false;
|
||||
public static bool ThreadingEnabled { get; set; }
|
||||
public override Expression<Action> TargetMethod => () => GenClosest.ClosestThing_Global(default, null, default, null, null);
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ClosestThing_Global_Replacement);
|
||||
private struct Variables
|
||||
{
|
||||
public IntVec3 center;
|
||||
public float maxDistanceSquared;
|
||||
public Func<Thing, float>? priorityGetter;
|
||||
public float closestDistSquared;
|
||||
public Predicate<Thing>? validator;
|
||||
}
|
||||
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable searchSet, float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? priorityGetter = null)
|
||||
{
|
||||
if (searchSet == null)
|
||||
return null;
|
||||
var closestDistSquared = 2.14748365E+09f;
|
||||
Thing? chosen = null;
|
||||
var bestPrio = float.MinValue;
|
||||
//float maxDistanceSquared = maxDistance * maxDistance;
|
||||
Variables variables = new() { center = center, maxDistanceSquared = maxDistance * maxDistance, validator = validator, priorityGetter = priorityGetter, closestDistSquared = closestDistSquared };
|
||||
|
||||
var predicate = GenClosest_Predicate.GetValidator(validator?.Target)?.Target ?? validator?.Target;
|
||||
if (ThreadingEnabled && validator != null && JobGiver_Work_Predicate.GetScanner(predicate) is WorkGiver_DoBill /*or WorkGiver_ConstructDeliverResources or WorkGiver_ConstructFinishFrames*/)
|
||||
{
|
||||
if (searchSet is IList<Thing> thingList)
|
||||
{
|
||||
Log.Message($"Running threading code for {validator.Target.GetType()}");
|
||||
Exception? exception = null;
|
||||
try
|
||||
{
|
||||
Parallel.For<(IList<Thing> list, Variables vars)>(0, thingList.Count,
|
||||
() => (thingList, variables),
|
||||
(i, loop, listAndVars) => (listAndVars.list, ProcessThreaded(listAndVars.list[i], listAndVars.vars)),
|
||||
_ => { });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
Log.Error($"Caught error while threading:\n{ex}");
|
||||
}
|
||||
if (exception is null)
|
||||
Log.Message("Finished threading code without errors");
|
||||
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
switch (searchSet)
|
||||
{
|
||||
case IList<Thing> thingList:
|
||||
{
|
||||
for (var i = 0; i < thingList.Count; i++)
|
||||
Process(thingList[i], variables);
|
||||
break;
|
||||
}
|
||||
case IList<Pawn> pawnList:
|
||||
{
|
||||
for (var j = 0; j < pawnList.Count; j++)
|
||||
Process(pawnList[j], variables);
|
||||
break;
|
||||
}
|
||||
case IList<Building> buildingList:
|
||||
{
|
||||
for (var k = 0; k < buildingList.Count; k++)
|
||||
Process(buildingList[k], variables);
|
||||
break;
|
||||
}
|
||||
case IList<IAttackTarget> attackTargetList:
|
||||
{
|
||||
for (var l = 0; l < attackTargetList.Count; l++)
|
||||
Process((Thing)attackTargetList[l], variables);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
foreach (Thing item in searchSet)
|
||||
Process(item, variables);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chosen;
|
||||
void Process<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
float num = (variables.center - t.Position).LengthHorizontalSquared;
|
||||
if (!(num > variables.maxDistanceSquared)
|
||||
&& (variables.priorityGetter != null || num < closestDistSquared)
|
||||
&& (variables.validator == null || variables.validator(t)))
|
||||
{
|
||||
var num2 = 0f;
|
||||
if (variables.priorityGetter != null)
|
||||
{
|
||||
num2 = variables.priorityGetter(t);
|
||||
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
|
||||
return;
|
||||
}
|
||||
chosen = t;
|
||||
closestDistSquared = variables.closestDistSquared = num;
|
||||
bestPrio = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
Variables ProcessThreaded<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
float num = (variables.center - t.Position).LengthHorizontalSquared;
|
||||
if (!(num > variables.maxDistanceSquared)
|
||||
&& (variables.priorityGetter != null || (num < variables.closestDistSquared && num < (variables.closestDistSquared = closestDistSquared)))
|
||||
&& (variables.validator == null || variables.validator(t)))
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var num2 = 0f;
|
||||
if (variables.priorityGetter != null)
|
||||
{
|
||||
num2 = variables.priorityGetter(t);
|
||||
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
|
||||
return variables;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (num >= closestDistSquared)
|
||||
return variables;
|
||||
}
|
||||
chosen = t;
|
||||
closestDistSquared = variables.closestDistSquared = num;
|
||||
bestPrio = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
private static Type JobGiverWorkPredicateType { get; } = AccessTools.FirstInner(typeof(JobGiver_Work),
|
||||
t => AccessTools.Field(t, "scanner") != null && AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage")) != null);
|
||||
private static JobGiver_Work_PredicateAccess JobGiver_Work_Predicate { get; }
|
||||
= (JobGiver_Work_PredicateAccess)Activator.CreateInstance(typeof(JobGiver_Work_PredicateAccessImplementation<>).MakeGenericType(JobGiverWorkPredicateType));
|
||||
private static Type GenClosestPredicateType { get; } = AccessTools.FirstInner(typeof(GenClosest),
|
||||
t => AccessTools.Field(t, "validator") != null && AccessTools.FirstMethod(t, m => m.Name.Contains("ClosestThingReachable")) != null);
|
||||
private static GenClosest_PredicateAccess GenClosest_Predicate { get; }
|
||||
= (GenClosest_PredicateAccess)Activator.CreateInstance(typeof(GenClosest_PredicateAccessImplementation<>).MakeGenericType(GenClosestPredicateType));
|
||||
private abstract class JobGiver_Work_PredicateAccess
|
||||
{
|
||||
public abstract WorkGiver_Scanner? GetScanner(object? obj);
|
||||
}
|
||||
private class JobGiver_Work_PredicateAccessImplementation<T> : JobGiver_Work_PredicateAccess
|
||||
{
|
||||
public override WorkGiver_Scanner? GetScanner(object? obj) => obj is T t ? Scanner(t) : null;
|
||||
public static AccessTools.FieldRef<T, WorkGiver_Scanner> Scanner { get; } = AccessTools.FieldRefAccess<T, WorkGiver_Scanner>("scanner");
|
||||
}
|
||||
private abstract class GenClosest_PredicateAccess
|
||||
{
|
||||
public abstract Predicate<Thing>? GetValidator(object? obj);
|
||||
}
|
||||
private class GenClosest_PredicateAccessImplementation<T> : GenClosest_PredicateAccess
|
||||
{
|
||||
public override Predicate<Thing>? GetValidator(object? obj) => obj is T t ? Validator(t) : null;
|
||||
public static AccessTools.FieldRef<T, Predicate<Thing>> Validator { get; } = AccessTools.FieldRefAccess<T, Predicate<Thing>>("validator");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if threadingEnabled
|
||||
public class CanReach_Patch : FishPatch
|
||||
{
|
||||
protected CanReach_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Expression<Action> TargetMethod => () => default(Reachability)!.CanReach(default, null, default, default);
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Enter(Lock);
|
||||
}
|
||||
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Exit(Lock);
|
||||
return __exception;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
#endif
|
||||
//}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class JobGiver_HaulOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PotentialWorkThingsGlobal_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Haul), nameof(JobGiver_Haul.TryGiveJob));
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
var pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling = FishTranspiler.Call(() => default(ListerHaulables)!.ThingsPotentiallyNeedingHauling());
|
||||
|
||||
try
|
||||
{
|
||||
return codes.InsertAfter(
|
||||
c => c == pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling,
|
||||
new CodeInstruction[]
|
||||
{
|
||||
FishTranspiler.Argument(method, "pawn"),
|
||||
FishTranspiler.Call(SortedThingsPotentiallyNeedingHauling)
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
//Log.Error("Performance Fish failed to apply its JobGiver_Haul transpiler. This particular optimization will be doing nothing now");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn)
|
||||
{
|
||||
_comparer.rootCell = pawn.Position;
|
||||
things.Sort(_comparer);
|
||||
return things;
|
||||
}
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
using static PerformanceFish.TranspilerHelpers;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
{
|
||||
public class TryIssueJobPackage_InnerDelegate_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then threads the IsForbidden loop, sorts it, caches the result and skips any following duplicate checks";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.FindIncludingInnerTypes(typeof(JobGiver_Work),
|
||||
t => AccessTools.Field(t, "scanner") != null ? AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool)) : null);
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var shouldRemove = true;
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
if (shouldRemove)
|
||||
{
|
||||
if (code.Branches(out _))
|
||||
shouldRemove = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.opcode == Ret)
|
||||
shouldRemove = true;
|
||||
yield return code;
|
||||
}
|
||||
}
|
||||
//Replaces
|
||||
//(Thing t) => !t.IsForbidden(pawn) && scanner.HasJobOnThing(pawn, t);
|
||||
//with
|
||||
//(Thing t) => scanner.HasJobOnThing(pawn, t);
|
||||
}
|
||||
}
|
||||
|
||||
public class TryIssueJobPackage_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then threads the IsForbidden loop, sorts it, caches the result and skips any following duplicate checks";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Work), nameof(JobGiver_Work.TryIssueJobPackage));
|
||||
public static void Prefix(Pawn pawn)
|
||||
{
|
||||
PrefilteredPotentialWorkThingsGlobal.Clear();
|
||||
FishCache.ClearAll();
|
||||
CurrentPawn = pawn;
|
||||
}
|
||||
public static void Postfix()
|
||||
{
|
||||
PrefilteredPotentialWorkThingsGlobal.Clear();
|
||||
FishCache.ClearAll();
|
||||
CurrentPawn = null;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase original)
|
||||
{
|
||||
var pawn = GetLdarg(original, "pawn");
|
||||
|
||||
var scanner_pawn_PotentialWorkThingsGlobal = GetCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal));
|
||||
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType = GetCall(() => TryGetPrefilteredPotentialWorkThingsAnyType(null, null));
|
||||
|
||||
var scanner_PotentialWorkThingRequest = GetGetPropertyCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingRequest));
|
||||
var scanner_pawn_GetCachedThingRequest = GetCall(() => GetCachedThingRequest(null, null));
|
||||
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
if (code == scanner_pawn_PotentialWorkThingsGlobal)
|
||||
{
|
||||
yield return code;
|
||||
yield return pawn;
|
||||
yield return potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType;
|
||||
}
|
||||
else if (code == scanner_PotentialWorkThingRequest)
|
||||
{
|
||||
yield return pawn;
|
||||
yield return scanner_pawn_GetCachedThingRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ThingRequest GetCachedThingRequest(WorkGiver_Scanner scanner, Pawn pawn)
|
||||
{
|
||||
var originalRequest = scanner.PotentialWorkThingRequest;
|
||||
if (originalRequest is { singleDef: null, group: ThingRequestGroup.Undefined })
|
||||
return originalRequest;
|
||||
|
||||
var listerThings = pawn.Map.listerThings.ThingsMatching(originalRequest);
|
||||
var collection = listerThings.Count > 100 ? (scanner.PotentialWorkThingsGlobal(pawn) as ICollection<Thing>) : null;
|
||||
|
||||
var processedThings = TryGetPrefilteredPotentialWorkThingsAnyType(collection is null || listerThings.Count < collection.Count * 4 ? listerThings : collection, pawn);
|
||||
var fishCache = originalRequest.singleDef != null ? FishCache.ForDef(originalRequest.singleDef) : FishCache.ForGroup(originalRequest.group);
|
||||
fishCache.FilteredThings = processedThings as List<Thing> ?? new(processedThings);
|
||||
return new ThingRequest() { group = originalRequest.group, singleDef = fishCache };
|
||||
}
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsAnyType(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
=> originalWorkThings switch
|
||||
{
|
||||
List<Thing> => Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
|
||||
List<Pawn> => Generic<Pawn>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
|
||||
_ => GetPrefilteredPotentialWorkThingsUncached(originalWorkThings, pawn)
|
||||
};
|
||||
public static IEnumerable<Thing> GetPrefilteredPotentialWorkThingsUncached(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
=> (originalWorkThings is not ICollection<Thing> collection || collection.Count < 2048)
|
||||
? CheckForbidden(originalWorkThings, pawn)
|
||||
: Generic<Thing>.Threaded_CheckForbiddenAndAddToAnyCollection(collection, pawn);
|
||||
|
||||
public static IEnumerable<Thing> CheckForbidden(IEnumerable<Thing> things, Pawn pawn)
|
||||
{
|
||||
if (things is null)
|
||||
yield break;
|
||||
|
||||
foreach (var thing in things)
|
||||
{
|
||||
if (!thing.IsForbidden(pawn))
|
||||
yield return thing;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Generic<T> where T : Thing
|
||||
{
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
if (!originalWorkThings.Any())
|
||||
return originalWorkThings;
|
||||
|
||||
if (!PrefilteredPotentialWorkThingsGlobal.TryGetValue(originalWorkThings, out var cache))
|
||||
cache = RecalculatePrefilteredPotentialWorkThings(originalWorkThings, pawn);
|
||||
|
||||
return cache;
|
||||
}
|
||||
public static T[] RecalculatePrefilteredPotentialWorkThings(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
var inputList = originalWorkThings as List<T>;
|
||||
if (inputList.Count < 512)
|
||||
Sequential_CheckForbiddenAndAddToList(inputList, pawn);
|
||||
else
|
||||
Threaded_CheckForbiddenAndAddToList(inputList, pawn);
|
||||
|
||||
var cache = _tempThingList.ToArray();
|
||||
if (cache.Length < 1024)
|
||||
{
|
||||
var pawnPosition = pawn.Position;
|
||||
Array.Sort(cache, (a, b) => (pawnPosition - a.Position).LengthHorizontalSquared.CompareTo((pawnPosition - b.Position).LengthHorizontalSquared));
|
||||
}
|
||||
|
||||
PrefilteredPotentialWorkThingsGlobal[originalWorkThings] = cache;
|
||||
_tempThingList.Clear();
|
||||
|
||||
return cache;
|
||||
}
|
||||
private static void Sequential_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
var tempThingList = _tempThingList;
|
||||
var count = inputList.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (!inputList[i].IsForbidden(pawn))
|
||||
tempThingList.Add(inputList[i]);
|
||||
}
|
||||
}
|
||||
private static void Threaded_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
#if DEBUG
|
||||
DebugLog.Message("Running threading code");
|
||||
var stopwatch = new System.Diagnostics.Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
var partitioner = Partitioner.Create(0, inputList.Count);
|
||||
|
||||
Parallel.ForEach<Tuple<int, int>, (List<T> localList, List<T> sourceList, Pawn pawn)>(partitioner,
|
||||
() => new(new(), inputList, pawn),
|
||||
(range, state, vars) =>
|
||||
{
|
||||
for (int i = range.Item1; i < range.Item2; i++)
|
||||
{
|
||||
var item = vars.sourceList[i];
|
||||
if (!item.IsForbidden(vars.pawn))
|
||||
vars.localList.Add(item);
|
||||
}
|
||||
return vars;
|
||||
},
|
||||
vars =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_tempThingList.AddRange(vars.localList);
|
||||
}
|
||||
});
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
|
||||
#endif
|
||||
}
|
||||
public static List<T> Threaded_CheckForbiddenAndAddToAnyCollection(ICollection<T> inputCollection, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
#if DEBUG
|
||||
DebugLog.Message("Running threading code");
|
||||
var stopwatch = new System.Diagnostics.Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
|
||||
Parallel.ForEach<T, (List<T> localList, Pawn pawn)>(inputCollection,
|
||||
() => new(new(), pawn),
|
||||
(thing, state, vars) =>
|
||||
{
|
||||
if (!thing.IsForbidden(vars.pawn))
|
||||
vars.localList.Add(thing);
|
||||
return vars;
|
||||
},
|
||||
vars =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_tempThingList.AddRange(vars.localList);
|
||||
}
|
||||
});
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
|
||||
#endif
|
||||
return _tempThingList;
|
||||
}
|
||||
|
||||
private static object _lock { get; } = new();
|
||||
private static List<T> _tempThingList { get; } = new();
|
||||
}
|
||||
|
||||
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; } = new();
|
||||
}
|
||||
|
||||
public class ThingRequest_IsUndefined_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
|
||||
public class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
|
||||
public static bool Prefix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
return false;
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> ((FishCache)__instance.singleDef).IsSingleDef
|
||||
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
|
||||
}
|
||||
|
||||
public class ListerThings_ThingsMatching_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var cachedThings = ((FishCache)req.singleDef).FilteredThings ?? ListerThings.EmptyList;
|
||||
if (__instance.use == ListerThingsUse.Region && cachedThings.Count > 20)
|
||||
{
|
||||
var regionalThings = GetForRegion(__instance, req);
|
||||
return regionalThings.Count < cachedThings.Count ? regionalThings : cachedThings;
|
||||
}
|
||||
else
|
||||
{
|
||||
return cachedThings;
|
||||
}
|
||||
}
|
||||
public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req)
|
||||
=> new(TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
|
||||
(((FishCache)req.singleDef) is var cache && cache.IsSingleDef
|
||||
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null
|
||||
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn));
|
||||
}
|
||||
|
||||
public class FishCache : ThingDef
|
||||
{
|
||||
public bool IsSingleDef => Def != null;
|
||||
public ThingDef Def { get; private set; }
|
||||
public List<Thing> FilteredThings { get; set; }
|
||||
public static FishCache ForDef(ThingDef def)
|
||||
{
|
||||
if (_dictOfDefs.TryGetValue(def, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfDefs[def] = value = new(def.defName) { Def = def };
|
||||
return value;
|
||||
}
|
||||
public static FishCache ForGroup(ThingRequestGroup group)
|
||||
{
|
||||
if (_dictOfGroups.TryGetValue(group, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfGroups[group] = value = new(group.ToString());
|
||||
return value;
|
||||
}
|
||||
public static void ClearAll()
|
||||
{
|
||||
_dictOfGroups.Clear();
|
||||
_dictOfDefs.Clear();
|
||||
}
|
||||
private FishCache(string name) => defName = name;
|
||||
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new();
|
||||
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
|
||||
}
|
||||
|
||||
private static Pawn CurrentPawn { get; set; }
|
||||
}*/
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using Verse.AI;
|
||||
using IngredientCacheList = PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs, bool Found)>;
|
||||
using RecipeIngredientCache = PerformanceFish.Cache.ByIndex<RimWorld.Bill, PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
{
|
||||
public static List<Type> BlackListedModExtensions => _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
|
||||
private static List<Type>? _blackListedModExtensions;
|
||||
|
||||
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes WorkGiver_DoBill to only check things that get accepted by a bill's filter for its ingredient lookup, instead of looping over everything and then checking filters afterwards. " +
|
||||
"Also caches these filter acceptances. Large performance impact.";
|
||||
|
||||
public override MethodBase TargetMethodInfo
|
||||
=> AccessTools.FirstMethod(DelegateInstanceType, m
|
||||
=> m.GetParameters() is { Length: 1 } parms
|
||||
&& parms[0].ParameterType == typeof(Region)
|
||||
&& m.ReturnType == typeof(bool)
|
||||
&& PatchProcessor.ReadMethodBody(m).Any(pair
|
||||
=> (pair.Key == OpCodes.Call || pair.Key == OpCodes.Callvirt)
|
||||
&& pair.Value is MethodInfo info
|
||||
&& info == AccessTools.Method(typeof(ListerThings), nameof(ListerThings.ThingsMatching))));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase method)
|
||||
=> Reflection.MakeReplacementCall(
|
||||
AccessTools.Method(typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(Replacement), generics: new[] { DelegateInstanceType }));
|
||||
|
||||
public static bool Replacement<T>(T __instance, Region r) where T : class
|
||||
{
|
||||
var instance = _delegateInstance.Get(__instance);
|
||||
var thingValidator = _thingValidatorInstance.Get(instance.ThingValidator.Target);
|
||||
|
||||
List<Thing> list;
|
||||
if (thingValidator is null) // <-- TryFindBestFixedIngredients, used by biosculptors, goes here. Fuck them
|
||||
{
|
||||
list = GetDefaultList(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bill = thingValidator.Bill;
|
||||
|
||||
if (HasNoFilters(bill) // apparently applies to a rim reborn benches?
|
||||
|| (BlackListedModExtensions.Exists(b => b != null) && IsBlackListed(bill))) // <-- Fuck these too
|
||||
{
|
||||
list = GetDefaultList(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var cache = ref RecipeIngredientCache.Get.GetReference(bill);
|
||||
if (cache.thingDefs is null || cache.ShouldRefreshNow)
|
||||
{
|
||||
cache.thingDefs = UpdateCache(bill, r, cache.thingDefs);
|
||||
cache.ShouldRefreshNow = false;
|
||||
RecipeIngredientCache.Get[bill] = cache;
|
||||
}
|
||||
list = GetList(r.listerThings.listsByDef, cache.thingDefs);
|
||||
}
|
||||
}
|
||||
|
||||
ActualLoop(list, r, instance.Pawn, instance.BaseValidator, instance.BillGiverIsPawn);
|
||||
|
||||
//list.Clear();
|
||||
|
||||
instance.RegionsProcessed++;
|
||||
if (WorkGiver_DoBill.newRelevantThings.Count > 0 && instance.RegionsProcessed > instance.AdjacentRegionsAvailable)
|
||||
{
|
||||
//WorkGiver_DoBill.relevantThings.AddRange(WorkGiver_DoBill.newRelevantThings);
|
||||
InsertAtCorrectPosition(WorkGiver_DoBill.relevantThings, WorkGiver_DoBill.newRelevantThings, instance.Pawn);
|
||||
|
||||
WorkGiver_DoBill.newRelevantThings.Clear();
|
||||
if (instance.FoundAllIngredientsAndChoose(WorkGiver_DoBill.relevantThings))
|
||||
{
|
||||
instance.FoundAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasNoFilters(Bill bill)
|
||||
=> bill.ingredientFilter is null
|
||||
|| bill.recipe.fixedIngredientFilter is null;
|
||||
|
||||
/*private static bool HasNoFilters(Bill bill)
|
||||
{
|
||||
if (bill.ingredientFilter is null
|
||||
|| bill.recipe.fixedIngredientFilter is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var ingredients = bill.recipe.ingredients;
|
||||
for (var i = 0; i < ingredients.Count; i++)
|
||||
{
|
||||
if (ingredients[i].filter is null)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
private static bool IsBlackListed(Bill bill)
|
||||
=> bill is null
|
||||
|| (bill.recipe.modExtensions?.Exists(e
|
||||
=> BlackListedModExtensions.Contains(e.GetType()))
|
||||
?? false);
|
||||
|
||||
private static List<Thing> GetDefaultList(Region r) => r.ListerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
|
||||
|
||||
public static void InsertAtCorrectPosition(List<Thing> list, List<Thing> thingsToInsert, Pawn pawn)
|
||||
{
|
||||
var count = thingsToInsert.Count;
|
||||
var comparer = _insertAtCorrectPositionComparer;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var thing = thingsToInsert[i];
|
||||
comparer.rootCell = WorkGiver_DoBill.GetBillGiverRootCell(thingsToInsert[i], pawn);
|
||||
|
||||
var index = ~list.BinarySearch(thing, comparer);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
list.Insert(index, thing);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new();
|
||||
|
||||
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator, bool billGiverIsPawn)
|
||||
{
|
||||
var count = list.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var thing = list[i];
|
||||
if (!WorkGiver_DoBill.processedThings.Contains(thing)
|
||||
&& ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch, pawn) && !(billGiverIsPawn && thing.def.IsMedicine) && baseValidator(thing))
|
||||
{
|
||||
WorkGiver_DoBill.newRelevantThings.Add(thing);
|
||||
WorkGiver_DoBill.processedThings.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Thing> GetList(Dictionary<ThingDef, List<Thing>> listsByDef, IngredientCacheList cacheList)
|
||||
{
|
||||
var list = _tempListForIngredients;
|
||||
list.Clear();
|
||||
var set = _tempSetForIngredientDefs;
|
||||
set.Clear();
|
||||
|
||||
for (var i = 0; i < cacheList.Count; i++)
|
||||
{
|
||||
var (defs, found) = cacheList[i].Value;
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
var count = defs.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (listsByDef.TryGetValue(defs[j], out var listByDef) && listByDef.Any())
|
||||
{
|
||||
if (i > 0 && !set.Add(defs[j]))
|
||||
continue;
|
||||
|
||||
list.AddRange(listByDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IngredientCacheList UpdateCache(Bill bill, Region r, IngredientCacheList? cacheList)
|
||||
{
|
||||
var recipe = bill.recipe;
|
||||
var ingredients = recipe.ingredients;
|
||||
var listsByDef = r.Map.listerThings.listsByDef;
|
||||
var result = cacheList ?? new(3);
|
||||
|
||||
for (var i = 0; i < ingredients.Count; i++) // generally not larger than 3 in vanilla and anything that tries to be
|
||||
{
|
||||
if (result.TryGetValue(ingredients[i], out var ingredientList))
|
||||
{
|
||||
ingredientList.Defs.Clear();
|
||||
ingredientList.Found = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[ingredients[i]] = ingredientList = new(new(), false);
|
||||
}
|
||||
|
||||
var filter = ingredients[i].filter;
|
||||
var allowedDefCount = filter.allowedDefs.Count;
|
||||
|
||||
if (allowedDefCount < 1)
|
||||
continue;
|
||||
|
||||
if (allowedDefCount == 1)
|
||||
{
|
||||
ingredientList.Defs.Add(filter.allowedDefs.First());
|
||||
continue;
|
||||
}
|
||||
|
||||
var smallestFilter = bill.ingredientFilter;
|
||||
var mediumFilter = recipe.fixedIngredientFilter;
|
||||
var largestFilter = filter;
|
||||
|
||||
OrderFiltersBySize(ref smallestFilter, ref mediumFilter, ref largestFilter);
|
||||
|
||||
var cachedSmallestList = AllowedDefsListCache.GetValue(smallestFilter).defs;
|
||||
if (cachedSmallestList.Count != smallestFilter.allowedDefs.Count)
|
||||
{
|
||||
// Log.Warning($"Cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}. Forcing a refresh now to fix.");
|
||||
ThingFilterPatches.ForceSynchronizeCache(smallestFilter);
|
||||
// Log.Warning($"New cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}.");
|
||||
// didn't trigger in 1.3, but now does. Fuck
|
||||
}
|
||||
var smallestListCount = cachedSmallestList.Count;
|
||||
for (var k = 0; k < smallestListCount; k++)
|
||||
{
|
||||
var def = cachedSmallestList[k];
|
||||
if (listsByDef.TryGetValue(def, out var listByDef) && listByDef.Any()
|
||||
&& mediumFilter.Allows(def)
|
||||
&& largestFilter.Allows(def))
|
||||
{
|
||||
ingredientList.Defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter, ref ThingFilter largestFilter)
|
||||
{
|
||||
if (smallestFilter.allowedDefs.Count > mediumFilter.allowedDefs.Count)
|
||||
(smallestFilter, mediumFilter) = (mediumFilter, smallestFilter);
|
||||
|
||||
if (smallestFilter.allowedDefs.Count > largestFilter.allowedDefs.Count)
|
||||
(smallestFilter, largestFilter) = (largestFilter, smallestFilter);
|
||||
|
||||
if (mediumFilter.allowedDefs.Count > largestFilter.allowedDefs.Count)
|
||||
(mediumFilter, largestFilter) = (largestFilter, mediumFilter);
|
||||
}
|
||||
|
||||
private static readonly List<Thing> _tempListForIngredients = new();
|
||||
private static readonly HashSet<ThingDef> _tempSetForIngredientDefs = new();
|
||||
}
|
||||
|
||||
private static Type DelegateInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>")) != null
|
||||
&& AccessTools.Field(t, "pawn") != null
|
||||
&& AccessTools.Field(t, "baseValidator") != null
|
||||
&& AccessTools.Field(t, "billGiverIsPawn") != null
|
||||
&& AccessTools.Field(t, "regionsProcessed") != null
|
||||
&& AccessTools.Field(t, "adjacentRegionsAvailable") != null
|
||||
&& AccessTools.Field(t, "foundAllIngredientsAndChoose") != null
|
||||
&& AccessTools.Field(t, "foundAll") != null
|
||||
&& AccessTools.Field(t, "thingValidator") != null)
|
||||
?? throw new("Performance Fish failed to find the target type for its WorkGiver_DoBill patch. This means that specific patch won't apply.");
|
||||
|
||||
private static Type ThingValidatorInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestBillIngredients)}>")) != null
|
||||
&& AccessTools.Field(t, "bill") != null
|
||||
//&& AccessTools.Field(t, "chosen") != null
|
||||
//&& AccessTools.Field(t, "billGiver") != null
|
||||
/*&& AccessTools.Field(t, "pawn") != null*/)
|
||||
?? throw new("Performance Fish failed to find the thingValidator type for its WorkGiver_DoBill patch. This likely flat out breaks the entire patch.");
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
private static DelegateInstance _delegateInstance { get; }
|
||||
= (DelegateInstance)Activator.CreateInstance(typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
private static ThingValidatorInstance _thingValidatorInstance { get; }
|
||||
= (ThingValidatorInstance)Activator.CreateInstance(typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
|
||||
|
||||
private abstract class DelegateInstance
|
||||
{
|
||||
public abstract DelegateInstance Get(object obj);
|
||||
|
||||
public abstract Pawn Pawn { get; set; }
|
||||
public abstract Predicate<Thing> BaseValidator { get; set; }
|
||||
public abstract bool BillGiverIsPawn { get; set; }
|
||||
public abstract int RegionsProcessed { get; set; }
|
||||
public abstract int AdjacentRegionsAvailable { get; set; }
|
||||
public abstract Predicate<List<Thing>> FoundAllIngredientsAndChoose { get; set; }
|
||||
public abstract bool FoundAll { get; set; }
|
||||
public abstract Predicate<Thing> ThingValidator { get; set; }
|
||||
}
|
||||
|
||||
private class DelegateInstanceImplementation<T> : DelegateInstance where T : class
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private T _instance;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public override DelegateInstance Get(object obj)
|
||||
{
|
||||
_instance = (T)obj;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override Pawn Pawn { get => _pawn(_instance); set => _pawn(_instance) = value; }
|
||||
public override Predicate<Thing> BaseValidator { get => _baseValidator(_instance); set => _baseValidator(_instance) = value; }
|
||||
public override bool BillGiverIsPawn { get => _billGiverIsPawn(_instance); set => _billGiverIsPawn(_instance) = value; }
|
||||
public override int RegionsProcessed { get => _regionsProcessed(_instance); set => _regionsProcessed(_instance) = value; }
|
||||
public override int AdjacentRegionsAvailable { get => _adjacentRegionsAvailable(_instance); set => _adjacentRegionsAvailable(_instance) = value; }
|
||||
public override Predicate<List<Thing>> FoundAllIngredientsAndChoose { get => _foundAllIngredientsAndChoose(_instance); set => _foundAllIngredientsAndChoose(_instance) = value; }
|
||||
public override bool FoundAll { get => _foundAll(_instance); set => _foundAll(_instance) = value; }
|
||||
public override Predicate<Thing> ThingValidator { get => _thingValidator(_instance); set => _thingValidator(_instance) = value; }
|
||||
|
||||
private static AccessTools.FieldRef<T, Pawn> _pawn = AccessTools.FieldRefAccess<T, Pawn>("pawn");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _baseValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("baseValidator");
|
||||
private static AccessTools.FieldRef<T, bool> _billGiverIsPawn = AccessTools.FieldRefAccess<T, bool>("billGiverIsPawn");
|
||||
private static AccessTools.FieldRef<T, int> _regionsProcessed = AccessTools.FieldRefAccess<T, int>("regionsProcessed");
|
||||
private static AccessTools.FieldRef<T, int> _adjacentRegionsAvailable = AccessTools.FieldRefAccess<T, int>("adjacentRegionsAvailable");
|
||||
private static AccessTools.FieldRef<T, Predicate<List<Thing>>> _foundAllIngredientsAndChoose = AccessTools.FieldRefAccess<T, Predicate<List<Thing>>>("foundAllIngredientsAndChoose");
|
||||
private static AccessTools.FieldRef<T, bool> _foundAll = AccessTools.FieldRefAccess<T, bool>("foundAll");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
|
||||
}
|
||||
|
||||
private abstract class ThingValidatorInstance
|
||||
{
|
||||
public abstract ThingValidatorInstance? Get(object obj);
|
||||
|
||||
public abstract Bill Bill { get; set; }
|
||||
//public abstract Thing BillGiver { get; set; }
|
||||
}
|
||||
|
||||
private class ThingValidatorInstanceImplementation<T> : ThingValidatorInstance where T : class
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private T _instance;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public override ThingValidatorInstance? Get(object obj)
|
||||
{
|
||||
if (obj is T t)
|
||||
{
|
||||
_instance = t;
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override Bill Bill { get => _bill(_instance); set => _bill(_instance) = value; }
|
||||
//public override Thing BillGiver { get => _billGiver(_instance); set => _billGiver(_instance) = value; }
|
||||
|
||||
private static AccessTools.FieldRef<T, Bill> _bill = AccessTools.FieldRefAccess<T, Bill>("bill");
|
||||
//private static AccessTools.FieldRef<T, Thing> _billGiver = AccessTools.FieldRefAccess<T, Thing>("billGiver");
|
||||
}
|
||||
|
||||
public class TryFindBestIngredientsHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsHelper;
|
||||
|
||||
public static void Postfix(Predicate<Thing> thingValidator)
|
||||
{
|
||||
var thingValidatorInstance = _thingValidatorInstance.Get(thingValidator.Target);
|
||||
if (thingValidatorInstance is null)
|
||||
return;
|
||||
|
||||
var cache = RecipeIngredientCache.Get[thingValidatorInstance.Bill].thingDefs;
|
||||
if (cache is null)
|
||||
return;
|
||||
|
||||
var items = cache._items;
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
ref var item = ref items[i];
|
||||
if (item.Value.Found)
|
||||
item = new(item.Key, new(item.Value.Defs, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
|
||||
|
||||
public static void Prefix(ref bool alreadySorted) => alreadySorted = true;
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
var i_variable = FishTranspiler.FirstLocalVariable(method, typeof(int));
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
=> i + 3 < codes.Count
|
||||
&& codes[i] == i_variable.Load()
|
||||
&& codes[i + 1] == FishTranspiler.Constant(1) // i++;
|
||||
&& codes[i + 2] == FishTranspiler.Add
|
||||
&& codes[i + 3] == i_variable.Store(),
|
||||
code
|
||||
=> new[]
|
||||
{
|
||||
FishTranspiler.FirstLocalVariable(method, typeof(IngredientCount))
|
||||
.WithLabels(code.ExtractLabels()),
|
||||
FishTranspiler.FirstArgument(method, typeof(Bill)),
|
||||
FishTranspiler.Call(MarkIngredientCountAsFound),
|
||||
code
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill bill)
|
||||
{
|
||||
if (bill is null)
|
||||
return;
|
||||
|
||||
var cache = RecipeIngredientCache.Get[bill].thingDefs;
|
||||
if (cache is null)
|
||||
return;
|
||||
|
||||
ref var cacheItem = ref cache._items[cache.IndexOfKey(ingredientCount)];
|
||||
|
||||
cacheItem = new(cacheItem.Key, new(cacheItem.Value.Defs, true));
|
||||
}
|
||||
}
|
||||
|
||||
public class ThingPositionComparer : IComparer<Thing>
|
||||
{
|
||||
public IntVec3 rootCell;
|
||||
|
||||
public int Compare(Thing x, Thing y) => (x.Position - rootCell).LengthHorizontalSquared.CompareTo((y.Position - rootCell).LengthHorizontalSquared);
|
||||
}
|
||||
|
||||
public struct RecipeIngredientCacheValue
|
||||
{
|
||||
public IngredientCacheList thingDefs;
|
||||
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public RecipeIngredientCacheValue(IngredientCacheList thingDefs)
|
||||
{
|
||||
this.thingDefs = thingDefs;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class WorkGiver_HaulOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PotentialWorkThingsGlobal_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
|
||||
public static IEnumerable<Thing> Postfix(IEnumerable<Thing> __result, Pawn pawn)
|
||||
{
|
||||
if (pawn != null && __result is List<Thing> list)
|
||||
Sort(list, pawn);
|
||||
return __result;
|
||||
}
|
||||
private static void Sort(List<Thing> list, Pawn pawn)
|
||||
{
|
||||
_comparer.rootCell = pawn.Position;
|
||||
list.Sort(_comparer);
|
||||
|
||||
//Log.ErrorOnce(list.Select(t => t.Position.DistanceToSquared(pawn.Position)).ToStringSafeEnumerable(), 84736285);
|
||||
}
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Buildings : ClassWithFishPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Get Things from the ListerThings dictionary with a rather quick lookup instead of looping over every single element in the lists of listerBuildings
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Thing> ThingsOfDefFast(ListerThings listerThings, ThingDef def) => listerThings.listsByDef.TryGetValue(def) ?? ListerThings.EmptyList;
|
||||
|
||||
public class ColonistsHaveBuilding_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveBuilding(default(ThingDef));
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
__result = ContainsDefForColonists(lister, def);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColonistsHaveResearchBench_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveResearchBench();
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
foreach (var def in LazyInit.ResearchBenches)
|
||||
{
|
||||
if (ContainsDefForColonists(lister, def))
|
||||
{
|
||||
__result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static class LazyInit
|
||||
{
|
||||
public static ThingDef[] ResearchBenches = DefDatabase<ThingDef>.AllDefsListForReading.Where(def => def.thingClass == typeof(Building_ResearchBench)).ToArray();
|
||||
static LazyInit() { }
|
||||
}
|
||||
}
|
||||
|
||||
public class ColonistsHaveBuildingWithPowerOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveBuildingWithPowerOn(null);
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == playerFaction
|
||||
&& things[i].TryGetComp<CompPowerTrader>() is not { PowerOn: false })
|
||||
{
|
||||
__result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class AllBuildingsColonistOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.AllBuildingsColonistOfDef(null);
|
||||
public static bool Prefix(ListerBuildings __instance, ref IEnumerable<Building> __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
__result = AllBuildingsColonistOfDefReplacement(lister, def);
|
||||
return false;
|
||||
}
|
||||
public static IEnumerable<Building> AllBuildingsColonistOfDefReplacement(ListerThings lister, ThingDef def)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i] is Building building)
|
||||
{
|
||||
if (building.Faction == playerFaction)
|
||||
yield return building;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowWarning(def, things[i]);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AllBuildingsNonColonistOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.AllBuildingsNonColonistOfDef(null);
|
||||
public static bool Prefix(ListerBuildings __instance, ref IEnumerable<Building> __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
__result = AllBuildingsNonColonistOfDefReplacement(lister, def);
|
||||
return false;
|
||||
}
|
||||
public static IEnumerable<Building> AllBuildingsNonColonistOfDefReplacement(ListerThings lister, ThingDef def)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i] is Building building)
|
||||
{
|
||||
if (building.Faction != playerFaction)
|
||||
yield return building;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowWarning(def, things[i]);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if harmonyIsFailingOnThisOne
|
||||
public class AllBuildingsColonistOfClass_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethodExpression => () => default(ListerBuildings).AllBuildingsColonistOfClass<Building>();
|
||||
public static bool Prefix(ListerBuildings __instance, MethodBase __originalMethod, ref IEnumerable __result)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
var type = __originalMethod.GetGenericArguments()[0]; //<-- This always returns Building instead of the type it's actually meant to look for
|
||||
DebugLog.Message($"AllBuildingsColonistOfClass running for type {type.Name}");
|
||||
if (!Replacements.TryGetValue(type, out var replacement))
|
||||
Replacements.Add(type, (GenericReplacementInterface)Activator.CreateInstance(typeof(GenericReplacement<>).MakeGenericType(type)));
|
||||
|
||||
__result = replacement.Replacement(lister);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Dictionary<Type, GenericReplacementInterface> Replacements { get; } = new();
|
||||
public interface GenericReplacementInterface
|
||||
{
|
||||
public IEnumerable Replacement(ListerThings lister);
|
||||
}
|
||||
public class GenericReplacement<T> : GenericReplacementInterface where T : Building
|
||||
{
|
||||
public IEnumerable Replacement(ListerThings lister)
|
||||
{
|
||||
buildingsOfClass ??= DefDatabase<ThingDef>.AllDefsListForReading.Where(def => def.thingClass == typeof(T)).ToArray();
|
||||
foreach (var def in buildingsOfClass)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == Faction.OfPlayerSilentFail)
|
||||
yield return things[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ThingDef[] buildingsOfClass;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public class Map_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerBuildings optimizations. Without this patch mods like Colony Manager throw errors during loading";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Constructor(typeof(Map));
|
||||
public static void Postfix(Map __instance) => _tempMap = __instance;
|
||||
}
|
||||
|
||||
private static Map? _tempMap; //mapComps get constructed before maps are added to Game.Maps, so this makes the patches work there too
|
||||
|
||||
public static void ThrowWarning(ThingDef def, Thing thing) => Log.Warning($"Tried to get building of def {def}, but its type is {thing.GetType()} instead of Building");
|
||||
|
||||
public static ListerThings? GetListerThings(ListerBuildings listerBuildings)
|
||||
{
|
||||
var maps = Current.Game.Maps;
|
||||
for (var i = 0; i < maps.Count; i++)
|
||||
{
|
||||
if (maps[i].listerBuildings == listerBuildings)
|
||||
return maps[i].listerThings;
|
||||
}
|
||||
|
||||
return _tempMap!.listerBuildings == listerBuildings ? _tempMap!.listerThings : null;
|
||||
}
|
||||
|
||||
public static bool ContainsDefForColonists(ListerThings listerThings, ThingDef def)
|
||||
{
|
||||
if (!listerThings.listsByDef.TryGetValue(def, out var things))
|
||||
return false;
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == playerFaction)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
global using HaulablesCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.ListerHaulables, PerformanceFish.Listers.Haulables.FishHaulableInfo>;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Haulables : ClassWithFishPatches
|
||||
{
|
||||
public class MapPreTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Throttling of the ListerHaulablesTick. This marks whether items should be moved from one stockpile to another. " +
|
||||
"When throttled these checks get spread out more evenly, helping against spikes that can happen with large deep storage containers.";
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(Map)!.MapPreTick();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes.MethodReplacer(new ListerHaulables(null).ListerHaulablesTick, ThreadedOrThrottled_ListerHaulablesTick);
|
||||
|
||||
public static void ThreadedOrThrottled_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
MainThread = Environment.CurrentManagedThreadId;
|
||||
|
||||
if (false && FishSettings.ThreadingEnabled)
|
||||
Threaded_ListerHaulablesTick(instance);
|
||||
else
|
||||
Throttled_ListerHaulablesTick(instance);
|
||||
}
|
||||
|
||||
//disabled
|
||||
public static void Threaded_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
DequeueQueuedThings(instance);
|
||||
|
||||
var currentCellCheckCount = GetCellCheckCount(instance);
|
||||
if (currentCellCheckCount is null)
|
||||
return;
|
||||
|
||||
if (_currentTask != null && !_currentTask.IsCompleted)
|
||||
{
|
||||
if (currentCellCheckCount.value > 1)
|
||||
currentCellCheckCount.value--;
|
||||
if (Volatile.Read(ref _queue) == 0)
|
||||
{
|
||||
Interlocked.Increment(ref _queue);
|
||||
_currentTask = _currentTask.ContinueWith(t =>
|
||||
{
|
||||
if (t.Exception is { } exception)
|
||||
Log.Error(exception.ToString());
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
_skippedTicks = 0;
|
||||
Interlocked.Decrement(ref _queue);
|
||||
ListerHaulablesTick_Patch.CurrentCellCheckCount = currentCellCheckCount.value;
|
||||
instance.ListerHaulablesTick();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_skippedTicks >= 9)
|
||||
{
|
||||
Log.Warning("Skipped 10 or more ListerHaulables ticks. This often means there's some kind of hauling error happening");
|
||||
_skippedTicks = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_skippedTicks++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentCellCheckCount.value < ListerHaulables.CellsPerTick)
|
||||
currentCellCheckCount.value++;
|
||||
}
|
||||
|
||||
if (_currentTask?.Exception is { } exception)
|
||||
Log.Error(exception.ToString());
|
||||
|
||||
_currentTask = Task.Run(() =>
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
_skippedTicks = 0;
|
||||
ListerHaulablesTick_Patch.CurrentCellCheckCount = currentCellCheckCount.value;
|
||||
instance.ListerHaulablesTick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void DequeueQueuedThings(ListerHaulables instance)
|
||||
{
|
||||
while (ThingsToRemove.TryDequeue(out var thing))
|
||||
TryRemove(instance, thing);
|
||||
|
||||
while (ThingsToAdd.TryDequeue(out var thing))
|
||||
TryAdd(instance, thing);
|
||||
}
|
||||
|
||||
public static void Throttled_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
var cellCheckCount = GetCellCheckCount(instance);
|
||||
if (cellCheckCount is null)
|
||||
return;
|
||||
|
||||
ListerHaulablesTick_Patch.CurrentCellCheckCount = cellCheckCount.value;
|
||||
Stopwatch.Start();
|
||||
try
|
||||
{
|
||||
instance.ListerHaulablesTick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex.ToString());
|
||||
}
|
||||
Stopwatch.Stop();
|
||||
if (Stopwatch.ElapsedTicks > 4000L)
|
||||
{
|
||||
if (cellCheckCount.value > 0)
|
||||
cellCheckCount.value--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cellCheckCount.value < 4)
|
||||
cellCheckCount.value++;
|
||||
}
|
||||
Stopwatch.Reset();
|
||||
}
|
||||
|
||||
private static ListerHaulablesTick_Patch.CellCheckCount? GetCellCheckCount(ListerHaulables instance)
|
||||
{
|
||||
var groupCycleIndex = ListerHaulables.groupCycleIndex + 1;
|
||||
if (groupCycleIndex >= int.MaxValue)
|
||||
groupCycleIndex = 0;
|
||||
if (groupCycleIndex == _lastGroupCycleIndex)
|
||||
{
|
||||
groupCycleIndex++;
|
||||
if (groupCycleIndex >= int.MaxValue)
|
||||
groupCycleIndex = 0;
|
||||
}
|
||||
_lastGroupCycleIndex = groupCycleIndex;
|
||||
|
||||
var allGroupsListForReading = instance.map.haulDestinationManager.AllGroupsListForReading;
|
||||
if (allGroupsListForReading.Count == 0)
|
||||
return null;
|
||||
|
||||
var num = groupCycleIndex % allGroupsListForReading.Count;
|
||||
var slotGroup = allGroupsListForReading[num];
|
||||
return slotGroup.CellsList.Count == 0 ? null
|
||||
: ListerHaulablesTick_Patch.CellCheckCountsBySlotGroup.GetOrCreateValue(slotGroup);
|
||||
}
|
||||
public static int MainThread { get; private set; }
|
||||
public static ConcurrentQueue<Thing> ThingsToRemove { get; } = new();
|
||||
public static ConcurrentQueue<Thing> ThingsToAdd { get; } = new();
|
||||
private static Stopwatch Stopwatch { get; } = new();
|
||||
|
||||
private static int _lastGroupCycleIndex;
|
||||
private static int _skippedTicks;
|
||||
private static int _queue;
|
||||
private static Task? _currentTask;
|
||||
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
|
||||
public class ListerHaulablesTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables throttling optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.ListerHaulablesTick();
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes //.MethodReplacer(GridsUtility.GetThingList, GetCopyOfThingList)
|
||||
.Manipulator(c => c.LoadsConstant(4), c =>
|
||||
{
|
||||
c.opcode = OpCodes.Call;
|
||||
c.operand = typeof(ListerHaulablesTick_Patch).GetProperty(nameof(CurrentCellCheckCount)).GetMethod;
|
||||
});
|
||||
/*public static List<Thing> GetCopyOfThingList(IntVec3 c, Map map)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
{
|
||||
PrivateThingList.ReplaceContentsWith(map.thingGrid.ThingsListAt(c));
|
||||
return PrivateThingList;
|
||||
}
|
||||
else
|
||||
{
|
||||
return map.thingGrid.ThingsListAt(c);
|
||||
}
|
||||
}*/
|
||||
public static int CurrentCellCheckCount { get; set; } = ListerHaulables.CellsPerTick;
|
||||
public static ConditionalWeakTable<SlotGroup, CellCheckCount> CellCheckCountsBySlotGroup { get; } = new();
|
||||
public class CellCheckCount
|
||||
{
|
||||
public int value = ListerHaulables.CellsPerTick;
|
||||
}
|
||||
//private static List<Thing> PrivateThingList { get; } = new();
|
||||
}
|
||||
|
||||
public static void TryAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
if (!cache.contains)
|
||||
{
|
||||
if (!instance.haulables.Contains(t))
|
||||
instance.haulables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//HaulablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThreadSafeTryAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
if (Environment.CurrentManagedThreadId == MapPreTick_Patch.MainThread || !FishSettings.ThreadingEnabled)
|
||||
{
|
||||
TryAdd(instance, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GetAndCheckHaulablesCache(instance, t).contains)
|
||||
MapPreTick_Patch.ThingsToAdd.Enqueue(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void TryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
if (cache.contains)
|
||||
{
|
||||
instance.haulables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//HaulablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThreadSafeTryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
if (Environment.CurrentManagedThreadId == MapPreTick_Patch.MainThread || !FishSettings.ThreadingEnabled)
|
||||
{
|
||||
TryRemove(instance, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetAndCheckHaulablesCache(instance, t).contains)
|
||||
MapPreTick_Patch.ThingsToRemove.Enqueue(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Check_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization for haulables checking";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.Check(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Check(ListerHaulables instance, Thing t)
|
||||
{
|
||||
//So the situation here is this: Vanilla RimWorld normally checks 4 storage cells per tick for valid haul destinations, usually with the intent of reaching higher priority storage.
|
||||
//This check itself can get quite expensive, but 4 of them aren't at all enough to be worth bothering with.
|
||||
//Now problems arise when LWM's deep storage comes into play: You put 5 stacks of items in one cell and suddenly 4 checks turn into 20.
|
||||
//20 hauling checks in turn is a number large enough to cause performance issues in colonies with high wealth.
|
||||
|
||||
if (instance.ShouldBeHaulable(t))
|
||||
ThreadSafeTryAdd(instance, t);
|
||||
else
|
||||
ThreadSafeTryRemove(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Check);
|
||||
}
|
||||
|
||||
public class TryRemove_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.TryRemove(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
if (t.def.category == ThingCategory.Item)
|
||||
ThreadSafeTryRemove(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(TryRemove);
|
||||
}
|
||||
|
||||
public class CheckAdd_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.CheckAdd(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CheckAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
|
||||
if (cache.contains || !instance.ShouldBeHaulable(t))
|
||||
return;
|
||||
|
||||
ThreadSafeTryAdd(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(CheckAdd);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FishHaulableInfo GetAndCheckHaulablesCache(ListerHaulables lister, Thing thing)
|
||||
{
|
||||
var key = new HaulablesCache(thing, lister);
|
||||
return HaulablesCache.GetValue(ref key);
|
||||
}
|
||||
|
||||
public struct FishHaulableInfo : Cache.IIsRefreshable<HaulablesCache, FishHaulableInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool contains;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public FishHaulableInfo SetNewValue(HaulablesCache key)
|
||||
{
|
||||
contains = key.Second.haulables.Contains(key.First);
|
||||
ShouldRefreshNow = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
global using MergeablesCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.ListerMergeables, PerformanceFish.Listers.Mergeables.FishMergeableInfo>;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Mergeables : ClassWithFishPatches
|
||||
{
|
||||
public class Check_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization for mergeables checking";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.Check(null);
|
||||
public static void Check(ListerMergeables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
|
||||
if (cache.contains)
|
||||
{
|
||||
if (!instance.ShouldBeMergeable(t))
|
||||
{
|
||||
instance.mergeables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (instance.ShouldBeMergeable(t))
|
||||
{
|
||||
if (!instance.mergeables.Contains(t))
|
||||
instance.mergeables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Check);
|
||||
}
|
||||
|
||||
public class CheckAdd_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerMergeables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.CheckAdd(null);
|
||||
public static void CheckAdd(ListerMergeables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
|
||||
if (cache.contains || !instance.ShouldBeMergeable(t))
|
||||
return;
|
||||
|
||||
if (!instance.mergeables.Contains(t))
|
||||
instance.mergeables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(CheckAdd);
|
||||
}
|
||||
|
||||
public class TryRemove_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerMergeables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.TryRemove(null);
|
||||
public static void TryRemove(ListerMergeables instance, Thing t)
|
||||
{
|
||||
if (t.def.category == ThingCategory.Item)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
|
||||
if (!cache.contains)
|
||||
return;
|
||||
|
||||
instance.mergeables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
//MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(TryRemove);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FishMergeableInfo GetAndCheckMergeablesCache(ListerMergeables lister, Thing thing)
|
||||
{
|
||||
var key = new MergeablesCache(thing, lister);
|
||||
return MergeablesCache.GetValue(ref key);
|
||||
}
|
||||
|
||||
public struct FishMergeableInfo : Cache.IIsRefreshable<MergeablesCache, FishMergeableInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool contains;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public FishMergeableInfo SetNewValue(MergeablesCache key)
|
||||
{
|
||||
contains = key.Second.mergeables.Contains(key.First);
|
||||
ShouldRefreshNow = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
public class Misc : ClassWithFishPatches
|
||||
{
|
||||
public class Building_Destroy_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used for haulables caching. Items don't become haulable after deconstructing stockpiles without this patch";
|
||||
public override Expression<Action> TargetMethod => () => default(Building)!.Destroy(default);
|
||||
public static void Prefix(Building __instance)
|
||||
{
|
||||
if (__instance is not IHaulDestination)
|
||||
return;
|
||||
HaulablesCache.Get.Clear();
|
||||
MergeablesCache.Get.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using StorageSettingsCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.StorageSettings, PerformanceFish.Listers.StorageSettingsPatches.FishStorageSettingsInfo>;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class StorageSettingsPatches : ClassWithFishPatches
|
||||
{
|
||||
public class AllowedToAccept_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "StorageSettings caching";
|
||||
public override Expression<Action> TargetMethod => () => default(StorageSettings)!.AllowedToAccept(default(Thing));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool Prefix(StorageSettings __instance, Thing t, ref bool __result, out bool __state)
|
||||
{
|
||||
var key = new StorageSettingsCache(t, __instance);
|
||||
ref var cache = ref StorageSettingsCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.allowedToAccept;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(StorageSettings __instance, Thing t, bool __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
StorageSettingsCache.Get[new(t, __instance)] = new() { ShouldRefreshNow = false, allowedToAccept = __result };
|
||||
}
|
||||
}
|
||||
|
||||
public class TryNotifyChanged_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "StorageSettings cache resetting";
|
||||
public override Expression<Action> TargetMethod => () => default(StorageSettings)!.TryNotifyChanged();
|
||||
public static void Postfix(StorageSettings __instance)
|
||||
{
|
||||
StorageSettingsCache.Get.Clear();
|
||||
HaulablesCache.Get.Clear();
|
||||
MergeablesCache.Get.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public struct FishStorageSettingsInfo : Cache.IIsRefreshable<StorageSettingsCache, FishStorageSettingsInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool allowedToAccept;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public FishStorageSettingsInfo SetNewValue(StorageSettingsCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#if disabled
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Things : ClassWithFishPatches
|
||||
{
|
||||
public class ThingRequest_IsUndefined_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
|
||||
public class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
|
||||
public static bool Prefix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
return false;
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> ((FishCache)__instance.singleDef).IsSingleDef
|
||||
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
|
||||
}
|
||||
|
||||
public class Add_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Add(null);
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Remove_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Remove(null);
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Remove(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Clear_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Clear();
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class ThingsMatching_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var fishCache = (FishCache)req.singleDef;
|
||||
var cachedThings = fishCache.FilteredThings ?? ListerThings.EmptyList;
|
||||
/*if (__instance.use == ListerThingsUse.Region && cachedThings.Count > 20)
|
||||
{
|
||||
var regionalThings = GetForRegion(__instance, req);
|
||||
return regionalThings.Count < cachedThings.Count ? regionalThings : cachedThings;
|
||||
}
|
||||
else
|
||||
{
|
||||
return cachedThings;
|
||||
}*/
|
||||
return null;
|
||||
}
|
||||
/*public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req)
|
||||
=> new(TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
|
||||
(((FishCache)req.singleDef) is var cache && cache.IsSingleDef
|
||||
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null
|
||||
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn));*/
|
||||
}
|
||||
|
||||
public static CompStyleablesInfo GetCompStyleablesCache(ListerThings lister) => Cache<ListerThings, CompStyleablesInfo>.GetValue(lister);
|
||||
|
||||
public struct CompStyleablesInfo : ICanRefresh<ListerThings, CompStyleablesInfo>
|
||||
{
|
||||
public List<Thing> things;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public CompStyleablesInfo SetNewValue(ListerThings key)
|
||||
{
|
||||
if (things is null)
|
||||
things = new();
|
||||
else
|
||||
things.Clear();
|
||||
var allBuildings = key.listsByGroup[(int)ThingRequestGroup.BuildingArtificial];
|
||||
var count = allBuildings.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (allBuildings[i].TryGetComp<CompStyleable>() is { SourcePrecept: not null })
|
||||
things.Add(allBuildings[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class FishCache : ThingDef
|
||||
{
|
||||
public bool IsSingleDef => Def != null;
|
||||
public ThingDef Def { get; private set; }
|
||||
public List<Thing> FilteredThings { get; set; }
|
||||
public static FishCache ForDef(ThingDef def)
|
||||
{
|
||||
if (_dictOfDefs.TryGetValue(def, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfDefs[def] = value = new(def.defName) { Def = def };
|
||||
return value;
|
||||
}
|
||||
public static FishCache ForGroup(ThingRequestGroup group)
|
||||
{
|
||||
if (_dictOfGroups.TryGetValue(group, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfGroups[group] = value = new(group.ToString());
|
||||
return value;
|
||||
}
|
||||
public static void ClearAll()
|
||||
{
|
||||
_dictOfGroups.Clear();
|
||||
_dictOfDefs.Clear();
|
||||
}
|
||||
private FishCache(string name) => defName = name;
|
||||
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new();
|
||||
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public static class Log
|
||||
{
|
||||
public static void Message(string message) => _message(message);
|
||||
public static void Warning(string message) => _warning(message);
|
||||
public static void Error(string message) => _error(message);
|
||||
|
||||
public static class Config
|
||||
{
|
||||
public static Action<string> Message { set => _message = value; }
|
||||
public static Action<string> Warning { set => _warning = value; }
|
||||
public static Action<string> Error { set => _error = value; }
|
||||
}
|
||||
|
||||
private static Action<string> _message = Verse.Log.Message;
|
||||
private static Action<string> _warning = Verse.Log.Warning;
|
||||
private static Action<string> _error = Verse.Log.Error;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#if disabled
|
||||
namespace PerformanceFish;
|
||||
public static class MapPawnsPatches
|
||||
{
|
||||
public static List<Pawn> GetAllPawns(MapPawns instance)
|
||||
{
|
||||
List<Pawn> allPawnsUnspawned = instance.AllPawnsUnspawned;
|
||||
if (allPawnsUnspawned.Count == 0)
|
||||
return instance.pawnsSpawned;
|
||||
instance.allPawnsResult.Clear();
|
||||
instance.allPawnsResult.AddRange(instance.pawnsSpawned);
|
||||
instance.allPawnsResult.AddRange(allPawnsUnspawned);
|
||||
return instance.allPawnsResult;
|
||||
}
|
||||
|
||||
public static List<Pawn> AllPawnsUnspawned(MapPawns instance)
|
||||
{
|
||||
instance.allPawnsUnspawnedResult.Clear();
|
||||
ThingOwnerUtility.GetAllThingsRecursively(instance.map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), instance.allPawnsUnspawnedResult, allowUnreal: true, null, alsoGetSpawnedThings: false);
|
||||
for (int num = instance.allPawnsUnspawnedResult.Count - 1; num >= 0; num--)
|
||||
{
|
||||
if (instance.allPawnsUnspawnedResult[num].Dead)
|
||||
instance.allPawnsUnspawnedResult.RemoveAt(num);
|
||||
}
|
||||
return instance.allPawnsUnspawnedResult;
|
||||
}
|
||||
|
||||
public static List<Pawn> PawnsInFaction(MapPawns instance, Faction faction)
|
||||
{
|
||||
if (faction == null)
|
||||
{
|
||||
Log.Error("Called PawnsInFaction with null faction.");
|
||||
return new List<Pawn>();
|
||||
}
|
||||
if (!instance.pawnsInFactionResult.TryGetValue(faction, out var value))
|
||||
{
|
||||
value = new List<Pawn>();
|
||||
instance.pawnsInFactionResult.Add(faction, value);
|
||||
}
|
||||
value.Clear();
|
||||
var allPawns = instance.AllPawns;
|
||||
for (var i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].Faction == faction)
|
||||
value.Add(allPawns[i]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static List<Pawn> FreeHumanlikesOfFaction(MapPawns instance, Faction faction)
|
||||
{
|
||||
if (!instance.freeHumanlikesOfFactionResult.TryGetValue(faction, out var value))
|
||||
{
|
||||
value = new List<Pawn>();
|
||||
instance.freeHumanlikesOfFactionResult.Add(faction, value);
|
||||
}
|
||||
value.Clear();
|
||||
List<Pawn> allPawns = instance.AllPawns;
|
||||
for (int i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].Faction == faction && (allPawns[i].HostFaction == null || allPawns[i].IsSlave) && allPawns[i].RaceProps.Humanlike)
|
||||
value.Add(allPawns[i]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static List<Pawn> FreeColonistsAndPrisoners(MapPawns instance)
|
||||
{
|
||||
List<Pawn> freeColonists = instance.FreeColonists;
|
||||
List<Pawn> prisonersOfColony = instance.PrisonersOfColony;
|
||||
if (prisonersOfColony.Count == 0)
|
||||
return freeColonists;
|
||||
instance.freeColonistsAndPrisonersResult.Clear();
|
||||
instance.freeColonistsAndPrisonersResult.AddRange(freeColonists);
|
||||
instance.freeColonistsAndPrisonersResult.AddRange(prisonersOfColony);
|
||||
return instance.freeColonistsAndPrisonersResult;
|
||||
}
|
||||
|
||||
public static List<Pawn> PrisonersOfColony(MapPawns instance)
|
||||
{
|
||||
instance.prisonersOfColonyResult.Clear();
|
||||
List<Pawn> allPawns = instance.AllPawns;
|
||||
for (int i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].IsPrisonerOfColony)
|
||||
instance.prisonersOfColonyResult.Add(allPawns[i]);
|
||||
}
|
||||
return instance.prisonersOfColonyResult;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using GearMassCache = PerformanceFish.Cache.ByIndex<Verse.Pawn, PerformanceFish.MassUtilityCaching.GearMassCacheValue>;
|
||||
using InventoryMassCache = PerformanceFish.Cache.ByIndex<Verse.Pawn, PerformanceFish.MassUtilityCaching.InventoryMassCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class MassUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GearMass_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches pawn gear mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup => MassUtility.GearMass;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn p, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref GearMassCache.Get.GetReference(p);
|
||||
if (cache.ShouldRefreshNow(p))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.mass;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Pawn p, bool __state, float __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
GearMassCache.Get[p] = new(__result, p);
|
||||
}
|
||||
}
|
||||
|
||||
public class InventoryMass_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches pawn inventory mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup => MassUtility.InventoryMass;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn p, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref InventoryMassCache.Get.GetReference(p);
|
||||
if (cache.ShouldRefreshNow(p))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.mass;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Pawn p, bool __state, float __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
InventoryMassCache.Get[p] = new(__result, p);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GearMassCacheValue
|
||||
{
|
||||
public float mass;
|
||||
private int _nextRefreshTick;
|
||||
private int _equipmentListsState;
|
||||
public GearMassCacheValue(float mass, Pawn p)
|
||||
{
|
||||
this.mass = mass;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_equipmentListsState = GetEquipmentListsState(p);
|
||||
}
|
||||
private static int GetEquipmentListsState(Pawn p)
|
||||
{
|
||||
var result = p.apparel != null ? p.apparel.WornApparel.Version() : 0;
|
||||
if (p.equipment != null)
|
||||
result += p.equipment.AllEquipmentListForReading.Version();
|
||||
return result;
|
||||
}
|
||||
public bool ShouldRefreshNow(Pawn p) => _nextRefreshTick < Current.Game.tickManager.TicksGame || _equipmentListsState != GetEquipmentListsState(p);
|
||||
}
|
||||
|
||||
public struct InventoryMassCacheValue
|
||||
{
|
||||
public float mass;
|
||||
private int _nextRefreshTick;
|
||||
private int _inventoryListsState;
|
||||
public InventoryMassCacheValue(float mass, Pawn p)
|
||||
{
|
||||
this.mass = mass;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 256 + Math.Abs(Rand.Int % 256);
|
||||
_inventoryListsState = GetInventoryListsState(p); //shorter refresh interval because of stack sizes not being considered by the listVersion check
|
||||
}
|
||||
private static int GetInventoryListsState(Pawn p) => p.inventory?.innerContainer is { } container ? container.innerList.Version() : 0;
|
||||
public bool ShouldRefreshNow(Pawn p) => _nextRefreshTick < Current.Game.tickManager.TicksGame || _inventoryListsState != GetInventoryListsState(p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class MiscOptimizations : ClassWithFishPatches
|
||||
{
|
||||
public class GenList_ListFullCopy_Patch : FishPatch
|
||||
{
|
||||
public override void TryUnpatch() { } // prevent unpatching
|
||||
|
||||
public override string Description => "Minor optimization. Requires a full restart to toggle off due to a bug in harmony";
|
||||
public override Delegate TargetMethodGroup => GenList.ListFullCopy<object>; // harmony patches for all T types
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(IList source, ref IList __result)
|
||||
=> source is null || source.Count == 0
|
||||
|| (__result = FisheryLib.CollectionExtensions.TryNew(source, source.GetType().GetGenericArguments()[0])) is null;
|
||||
}
|
||||
|
||||
public class WindManager_WindManagerTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Throttles plant sway to update at most once per frame instead of every tick. Also fixes the plant sway setting to actually disable plant sway calculations when it should. " +
|
||||
"By default it simply sets values to 0 but then still goes through everything, sacrificing tps for no reason";
|
||||
public override Expression<Action> TargetMethod => () => default(WindManager)!.WindManagerTick();
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes)
|
||||
{
|
||||
var find_CurrentMap = FishTranspiler.CallPropertyGetter(typeof(Find), nameof(Find.CurrentMap));
|
||||
var this_map = FishTranspiler.Field(typeof(WindManager), nameof(WindManager.map));
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
=> i - 3 >= 0
|
||||
&& codes[i - 3] == find_CurrentMap
|
||||
&& codes[i - 1] == this_map
|
||||
&& codes[i].operand is Label, // goto return;
|
||||
|
||||
code
|
||||
=> new[]
|
||||
{
|
||||
code,
|
||||
FishTranspiler.Call(ShouldSway),
|
||||
FishTranspiler.IfFalse_Short((Label)code.operand)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static bool ShouldSway()
|
||||
{
|
||||
var currentFrame = Time.frameCount;
|
||||
if (_lastSwayFrame >= currentFrame)
|
||||
return false;
|
||||
|
||||
if (Prefs.PlantWindSway != _lastPrefValue)
|
||||
_lastPrefValue = Prefs.PlantWindSway;
|
||||
else if (!Prefs.PlantWindSway)
|
||||
return false;
|
||||
|
||||
_lastSwayFrame = currentFrame;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int _lastSwayFrame;
|
||||
private static bool _lastPrefValue;
|
||||
}
|
||||
|
||||
public class DrawBatch_Flush_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization of a fleck rendering method. Low - medium performance impact";
|
||||
public override Expression<Action> TargetMethod => () => default(DrawBatch)!.Flush(default);
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions CodeInstructions, ILGenerator generator)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
|
||||
var instance_tmpPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.tmpPropertyBlocks));
|
||||
var instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange = FishTranspiler.Call<Action<HashSet<DrawBatchPropertyBlock>, List<DrawBatchPropertyBlock>>>(GenCollection.AddRange);
|
||||
var instance_tmpPropertyBlocks_key_propertyBlock_Add = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().Add);
|
||||
|
||||
var label_inFrontOf_instance_batches_Clear = generator.DefineLabel();
|
||||
var instance_batches_Clear = FishTranspiler.Call(new Dictionary<DrawBatch.BatchKey, List<DrawBatch.BatchData>>().Clear);
|
||||
|
||||
codes[
|
||||
codes.FindIndex(c => c == instance_batches_Clear)
|
||||
- 2
|
||||
].labels.Add(label_inFrontOf_instance_batches_Clear);
|
||||
|
||||
var instance_myPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.myPropertyBlocks));
|
||||
var instance_myPropertyBlocks_GetEnumerator = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().GetEnumerator);
|
||||
|
||||
var addRangeRemoved = false;
|
||||
var addRemoved = false;
|
||||
var checkFleckLeaksInserted = false;
|
||||
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (i + 4 < codes.Count
|
||||
&& codes[i + 1] == instance_tmpPropertyBlocks
|
||||
&& codes[i + 4] == instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange)
|
||||
{
|
||||
i += 4;
|
||||
addRangeRemoved = true;
|
||||
}
|
||||
else if (i + 5 < codes.Count
|
||||
&& codes[i + 1] == instance_tmpPropertyBlocks
|
||||
&& codes[i + 4] == instance_tmpPropertyBlocks_key_propertyBlock_Add
|
||||
&& codes[i + 5].opcode == Pop)
|
||||
{
|
||||
i += 5;
|
||||
addRemoved = true;
|
||||
}
|
||||
else if (i + 2 < codes.Count
|
||||
&& codes[i + 1] == instance_myPropertyBlocks
|
||||
&& codes[i + 2] == instance_myPropertyBlocks_GetEnumerator)
|
||||
{
|
||||
yield return codes[i];
|
||||
yield return FishTranspiler.Call(CheckFleckLeaks);
|
||||
yield return FishTranspiler.IfFalse(label_inFrontOf_instance_batches_Clear);
|
||||
yield return FishTranspiler.This;
|
||||
checkFleckLeaksInserted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!addRangeRemoved || !addRemoved || !checkFleckLeaksInserted)
|
||||
Log.Error($"Performance Fish failed to apply its DrawBatch.Flush() patch. Mod Settings has an option to disable this");
|
||||
}
|
||||
|
||||
public static bool CheckFleckLeaks(DrawBatch instance)
|
||||
{
|
||||
if (instance.myPropertyBlocks.Count != instance.propertyBlockCache.Count)
|
||||
{
|
||||
instance.tmpPropertyBlocks.AddRange(instance.propertyBlockCache);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if whatItsGonnaLookLikeAfterTranspiling
|
||||
public static void Flush(DrawBatch __instance, bool draw = true)
|
||||
{
|
||||
if (__instance.tmpPropertyBlock == null)
|
||||
__instance.tmpPropertyBlock = new MaterialPropertyBlock();
|
||||
__instance.tmpPropertyBlocks.Clear();
|
||||
// __instance.tmpPropertyBlocks.AddRange(__instance.propertyBlockCache); // <--------------------------------------------------------- removed
|
||||
try
|
||||
{
|
||||
foreach (var batch in __instance.batches)
|
||||
{
|
||||
var key = batch.Key;
|
||||
try
|
||||
{
|
||||
foreach (var item in batch.Value)
|
||||
{
|
||||
var batchData = item;
|
||||
if (draw)
|
||||
{
|
||||
__instance.tmpPropertyBlock.Clear();
|
||||
if (key.propertyBlock != null)
|
||||
key.propertyBlock.Write(__instance.tmpPropertyBlock);
|
||||
if (key.renderInstanced)
|
||||
{
|
||||
key.material.enableInstancing = true;
|
||||
if (batchData.hasAnyColors)
|
||||
__instance.tmpPropertyBlock.SetVectorArray("_Color", batchData.colors);
|
||||
Graphics.DrawMeshInstanced(key.mesh, 0, key.material, item.matrices, item.ptr, __instance.tmpPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.On, receiveShadows: true, key.layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < batchData.ptr; i++)
|
||||
{
|
||||
var matrix = batchData.matrices[i];
|
||||
var vector = batchData.colors[i];
|
||||
if (batchData.hasAnyColors)
|
||||
__instance.tmpPropertyBlock.SetColor("_Color", vector);
|
||||
Graphics.DrawMesh(key.mesh, matrix, key.material, key.layer, null, 0, __instance.tmpPropertyBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
batchData.Clear();
|
||||
__instance.batchDataListCache.Add(batchData);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (key.propertyBlock != null && __instance.myPropertyBlocks.Contains(key.propertyBlock))
|
||||
{
|
||||
// __instance.tmpPropertyBlocks.Add(key.propertyBlock); // <---------------------------------------------------------- removed
|
||||
key.propertyBlock.Clear();
|
||||
__instance.propertyBlockCache.Add(key.propertyBlock);
|
||||
}
|
||||
__instance.batchListCache.Add(batch.Value);
|
||||
batch.Value.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (CheckFleckLeaks(__instance)) // <------------------------------------------------------------------------------------------ added
|
||||
{
|
||||
foreach (var myPropertyBlock in __instance.myPropertyBlocks)
|
||||
{
|
||||
if (!__instance.tmpPropertyBlocks.Contains(myPropertyBlock))
|
||||
Log.Warning("Property block from FleckDrawBatch leaked!" + ((myPropertyBlock.leakDebugString == null) ? null : ("Leak debug information: \n" + myPropertyBlock.leakDebugString)));
|
||||
}
|
||||
var hashSet = __instance.myPropertyBlocks;
|
||||
__instance.myPropertyBlocks = __instance.tmpPropertyBlocks;
|
||||
__instance.tmpPropertyBlocks = hashSet;
|
||||
}
|
||||
|
||||
__instance.batches.Clear();
|
||||
__instance.lastBatchKey = default;
|
||||
__instance.lastBatchList = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class Pawn_PsychicEntropyTrackerOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class Psylink_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string? Description => "Caches/throttles the psylink method to only update every 32 ticks.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker), nameof(Pawn_PsychicEntropyTracker.Psylink));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.GetCodeInstructions(Psylink);
|
||||
|
||||
public static Hediff_Psylink Psylink(Pawn_PsychicEntropyTracker __instance)
|
||||
{
|
||||
if (__instance.psylinkCachedForTick <= Find.TickManager.TicksGame)
|
||||
{
|
||||
__instance.psylinkCached = __instance.pawn.GetMainPsylinkSource();
|
||||
__instance.psylinkCachedForTick = Find.TickManager.TicksGame + 32;
|
||||
}
|
||||
return __instance.psylinkCached;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Optimize>true</Optimize>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Authors>bradson</Authors>
|
||||
<Copyright>Copyright (c) 2021 bradson</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<EnableNETAnalyzers>True</EnableNETAnalyzers>
|
||||
<AnalysisLevel>preview-all</AnalysisLevel>
|
||||
<DebugType>none</DebugType>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<Configurations>v1.3;v1.4</Configurations>
|
||||
</PropertyGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(Configuration)' == 'v1.3'">
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\1.3\Assemblies\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" />
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.3\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition="'$(Configuration)' == 'v1.4'">
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\1.4\Assemblies\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*-*" />
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.4\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.*" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" /><!-- Version included in Unity is missing IsNullRef -->
|
||||
<PackageReference Include="Nullable" Version="1.*" />
|
||||
<PackageReference Include="IndexRange" Version="1.*" ExcludeAssets="runtime" />
|
||||
<Publicize Include="Assembly-CSharp" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.buckets" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.entries" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.version" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2+Entry" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.List`1._items" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.List`1._version" />
|
||||
<Publicize Include="mscorlib:System.Collections.Generic.List`1._size" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeCast" />
|
||||
<None Include="..\..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.31911.260
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceFish", "PerformanceFish.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6948F7E6-7ED7-4716-87EF-CFF1F465F561}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
v1.3|Any CPU = v1.3|Any CPU
|
||||
v1.4|Any CPU = v1.4|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.3|Any CPU.ActiveCfg = v1.3|Any CPU
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.3|Any CPU.Build.0 = v1.3|Any CPU
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU
|
||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.4|Any CPU.Build.0 = v1.4|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D4907397-0B53-47A8-9B73-09C35415F989}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq.Expressions;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Threading;
|
||||
global using FisheryLib;
|
||||
global using FisheryLib.Utility.Diagnostics;
|
||||
global using HarmonyLib;
|
||||
global using PerformanceFish.Utility;
|
||||
global using RimWorld;
|
||||
global using UnityEngine;
|
||||
global using Verse;
|
||||
global using static FisheryLib.Aliases;
|
||||
global using CodeInstructions = System.Collections.Generic.IEnumerable<HarmonyLib.CodeInstruction>;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using PerformanceFish.System;
|
||||
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
[assembly: SecurityTransparent]
|
||||
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class PerformanceFishMod : Mod
|
||||
{
|
||||
public const string NAME = "Performance Fish";
|
||||
public const decimal VERSION = 0.3M;
|
||||
|
||||
#pragma warning disable IDE0079 // Visual Studio freaking out a bit over here
|
||||
#pragma warning disable CS8618
|
||||
public static FishSettings Settings { get; private set; }
|
||||
public static PerformanceFishMod Mod { get; private set; }
|
||||
public static Harmony Harmony { get; private set; }
|
||||
public static IHasFishPatch[] AllPatchClasses { get; private set; }
|
||||
#pragma warning restore CS8618, IDE0079
|
||||
|
||||
public PerformanceFishMod(ModContentPack content) : base(content)
|
||||
{
|
||||
DebugLog.Message($"Initializing {NAME} v{VERSION}");
|
||||
|
||||
if (Mod != null)
|
||||
ThrowHelper.ThrowInvalidOperationException($"Initialized a second instance of the {NAME} mod. This should never happen. Cancelling now to avoid further issues.");
|
||||
|
||||
Mod = this;
|
||||
|
||||
TryInitialize(EqualityComparerOptimization.Initialize);
|
||||
|
||||
Harmony = new(content.PackageIdPlayerFacing);
|
||||
|
||||
AllPatchClasses = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => typeof(IHasFishPatch).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
|
||||
.Select(type
|
||||
=> {
|
||||
try
|
||||
{
|
||||
return SingletonFactory<IHasFishPatch>.Get(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {type.Name}:\n{ex}");
|
||||
return null!;
|
||||
}
|
||||
})
|
||||
.Where(p => p != null)
|
||||
.ToArray();
|
||||
|
||||
ReflectionCaching.Initialize();
|
||||
|
||||
try
|
||||
{
|
||||
Settings = GetSettings<FishSettings>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an expection while trying to load its settings\n{ex}");
|
||||
WriteSettings();
|
||||
}
|
||||
|
||||
TryInitialize(AnalyzerFixes.Patch);
|
||||
|
||||
foreach (var patch in AllPatchClasses)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {patch.GetType().Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryInitialize(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {action.Method.FullDescription()}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public static T? Get<T>()
|
||||
{
|
||||
foreach (var patch in AllPatchClasses)
|
||||
{
|
||||
if (patch is T patchOfT)
|
||||
return patchOfT;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public override string SettingsCategory() => "Performance Fish";
|
||||
public override void DoSettingsWindowContents(Rect inRect) => FishSettings.DoSettingsWindowContents(inRect);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using RecipeUsersCache = PerformanceFish.Cache.ByIndex<Verse.RecipeDef, PerformanceFish.RecipeDefCaching.AllRecipeUsers_Patch.RecipeUsersCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class RecipeDefCaching : ClassWithFishPatches
|
||||
{
|
||||
public override bool RequiresLoadedGameForPatching => true;
|
||||
public class AllRecipeUsers_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the AllRecipeUsers method. Becomes impactful with lots of unfinished things around, as those check everything for potentially accepting them";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(RecipeDef), nameof(RecipeDef.AllRecipeUsers));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(RecipeDef __instance, ref IEnumerable<ThingDef> __result, out bool __state)
|
||||
{
|
||||
if (__instance is null || __instance.GetType() != typeof(RecipeDef))
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ref var cache = ref RecipeUsersCache.Get.GetReference(__instance);
|
||||
if (cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.recipeUsers;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(RecipeDef __instance, IEnumerable<ThingDef> __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
var cache = RecipeUsersCache.Get[__instance];
|
||||
|
||||
if (cache.recipeUsers is List<ThingDef> list)
|
||||
{
|
||||
list.Clear();
|
||||
list.AddRange(__result);
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.recipeUsers = new List<ThingDef>(__result);
|
||||
}
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
RecipeUsersCache.Get[__instance] = cache;
|
||||
}
|
||||
|
||||
public struct RecipeUsersCacheValue : Cache.IIsRefreshable<RecipeDef, RecipeUsersCacheValue>
|
||||
{
|
||||
public IEnumerable<ThingDef> recipeUsers;
|
||||
private int _nextRefreshFrame;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => Time.frameCount > _nextRefreshFrame;
|
||||
set => _nextRefreshFrame = value ? 0 : Time.frameCount + 4096 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public RecipeUsersCacheValue SetNewValue(RecipeDef key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/*public class PotentiallyMissingIngredients_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new RecipeDef().PotentiallyMissingIngredients(null, null);
|
||||
public static bool Prefix(RecipeDef __instance, Pawn billDoer, Map map, ref IEnumerable<ThingDef> __result)
|
||||
{
|
||||
if (!Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>.TryGetValue(__instance, billDoer, map, out var cache))
|
||||
return true;
|
||||
if (__instance.ingredients.Count != 0)
|
||||
{
|
||||
Log.Warning("RecipeDef ingredients mismatched its cache in Performance Fish, forcing a refresh");
|
||||
return true;
|
||||
}
|
||||
|
||||
__result = cache.ingredientThingDefs;
|
||||
return false;
|
||||
}
|
||||
public static void Postfix(RecipeDef __instance, Pawn billDoer, Map map, IEnumerable<ThingDef> __result, bool __runOriginal)
|
||||
{
|
||||
if (!__runOriginal)
|
||||
return;
|
||||
Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>.Get[new(__instance, billDoer, map)]
|
||||
= new() { ingredientThingDefs = __result.ToArray(), firstIngredientsItemCount = __instance.ingredients.FirstOrDefault()?.count ?? 0, ShouldRefreshNow = false };
|
||||
}
|
||||
|
||||
public static MissingIngredientsCache GetCache(RecipeDef def, Pawn billDoer, Map map) => Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>.GetValue(def, billDoer, map);
|
||||
public struct MissingIngredientsCache : ICanRefresh<Cache<RecipeDef, Pawn, Map, MissingIngredientsCache>, MissingIngredientsCache>
|
||||
{
|
||||
public ThingDef[] ingredientThingDefs;
|
||||
private int _nextRefreshFrame;
|
||||
public int firstIngredientsItemCount;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => Time.frameCount > _nextRefreshFrame;
|
||||
set => _nextRefreshFrame = value ? 0 : Time.frameCount + 2048 + Math.Abs(Rand.Int % 1024);
|
||||
}
|
||||
public MissingIngredientsCache SetNewValue(Cache<RecipeDef, Pawn, Map, MissingIngredientsCache> key) => throw new NotImplementedException();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public class GenDrawPatches : ClassWithFishPatches
|
||||
{
|
||||
public class DrawMeshNowOrLater_Patch : FishPatch
|
||||
{
|
||||
public override Delegate TargetMethodGroup => (Action<Mesh, Vector3, Quaternion, Material, bool>)GenDraw.DrawMeshNowOrLater;
|
||||
|
||||
public static void Replacement(Mesh mesh, Vector3 loc, Quaternion quat, Material mat, bool drawNow)
|
||||
{
|
||||
if (drawNow)
|
||||
{
|
||||
if (!mat.SetPass(0))
|
||||
Log.Error("SetPass(0) call failed on material " + mat.name + " with shader " + mat.shader.name);
|
||||
Graphics.DrawMeshNow(mesh, loc, quat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DrawMeshNowOrLaterWithMatrix_Patch : FishPatch
|
||||
{
|
||||
public override Delegate TargetMethodGroup => (Action<Mesh, Matrix4x4, Material, bool>)GenDraw.DrawMeshNowOrLater;
|
||||
|
||||
public static void Replacement(Mesh mesh, Matrix4x4 matrix, Material mat, bool drawNow)
|
||||
{
|
||||
if (drawNow)
|
||||
{
|
||||
mat.SetPass(0);
|
||||
Graphics.DrawMeshNow(mesh, matrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.DrawMesh(mesh, matrix, mat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public class GraphicPatches : ClassWithFishPatches
|
||||
{
|
||||
public class DrawMeshInt_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(Graphic), nameof(Graphic.DrawMeshInt));
|
||||
|
||||
public static void Replacement(Graphic __instance, Mesh mesh, Vector3 loc, Quaternion quat, Material mat)
|
||||
{
|
||||
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public class Shadow_DrawWorker_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(Graphic_Shadow), nameof(Graphic_Shadow.DrawWorker));
|
||||
|
||||
public static void Replacement(Graphic_Shadow __instance, Vector3 loc, Rot4 rot, ThingDef thingDef, Thing thing, float extraRotation)
|
||||
{
|
||||
if (__instance.shadowMesh is null
|
||||
|| thingDef is null
|
||||
|| __instance.shadowInfo is null
|
||||
|| (Find.CurrentMap is { } map && loc.ToIntVec3().InBounds(map) && map.roofGrid.Roofed(loc.ToIntVec3()))
|
||||
|| !DebugViewSettings.drawShadows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = loc + __instance.shadowInfo.offset;
|
||||
position.y = AltitudeLayer.Shadows.AltitudeFor();
|
||||
Graphics.DrawMesh(__instance.shadowMesh, position, rot.AsQuat, MatBases.SunShadowFade, 0);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public class PawnCacheRendererPatches : ClassWithFishPatches // The PawnCacheRenderer's Camera.Render() call has a considerable performance impact despite only being used for rendering of single pawns. These are failed attempts at lowering its impact.
|
||||
{
|
||||
private static bool _optimizeCameraEnabled;
|
||||
|
||||
public class OnPostRender_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(PawnCacheRenderer), nameof(PawnCacheRenderer.OnPostRender));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(OnPostRender);
|
||||
|
||||
public static void OnPostRender(PawnCacheRenderer __instance)
|
||||
{
|
||||
Find.PawnCacheCamera.cullingMask = DefaultCullingMask;
|
||||
__instance.pawn.Drawer.renderer.RenderCache(__instance.rotation, __instance.angle, __instance.positionOffset, __instance.renderHead, __instance.renderBody, __instance.portrait, __instance.renderHeadgear, __instance.renderClothes, __instance.overrideApparelColor, __instance.overrideHairColor, __instance.stylingStation);
|
||||
Find.PawnCacheCamera.cullingMask = 16; // random
|
||||
}
|
||||
|
||||
private static int DefaultCullingMask => _defaultCullinkMask ??= Find.PawnCacheCamera.cullingMask;
|
||||
private static int? _defaultCullinkMask;
|
||||
}
|
||||
|
||||
public class RenderPawn_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.DeclaredMethod(typeof(PawnCacheRenderer), nameof(PawnCacheRenderer.RenderPawn));
|
||||
|
||||
public static void Prefix(ref Vector3 cameraOffset, ref Vector3 positionOffset)
|
||||
{
|
||||
cameraOffset += new Vector3(2300f, 0f, 0f);
|
||||
positionOffset += new Vector3(2300f, 0f, 0f);
|
||||
}
|
||||
|
||||
private static int DefaultCullingMask => _defaultCullinkMask ??= Find.PawnCacheCamera.cullingMask;
|
||||
private static int? _defaultCullinkMask;
|
||||
}
|
||||
|
||||
public static bool OptimizeCameraEnabled
|
||||
{
|
||||
get => _optimizeCameraEnabled;
|
||||
set
|
||||
{
|
||||
_optimizeCameraEnabled = value;
|
||||
OptimizeCamera();
|
||||
}
|
||||
}
|
||||
|
||||
public static void OptimizeCamera()
|
||||
{
|
||||
var cacheCamera = Find.PawnCacheCamera;
|
||||
var camera = Find.Camera;
|
||||
camera.renderingPath = _optimizeCameraEnabled ? RenderingPath.VertexLit : DefaultCameraRenderingPath;
|
||||
cacheCamera.renderingPath = _optimizeCameraEnabled ? RenderingPath.VertexLit : DefaultCacheCameraRenderingPath;
|
||||
}
|
||||
|
||||
private static RenderingPath DefaultCameraRenderingPath => _defaultCameraRenderingPath ??= Find.Camera.renderingPath;
|
||||
private static RenderingPath DefaultCacheCameraRenderingPath => _defaultCacheCameraRenderingPath ??= Find.PawnCacheCamera.renderingPath;
|
||||
private static RenderingPath? _defaultCameraRenderingPath;
|
||||
private static RenderingPath? _defaultCacheCameraRenderingPath;
|
||||
}*/
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using RoomOwnersCache = PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.FishRoomOwnersInfo>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
||||
{
|
||||
public string Description => "Room stats related optimizations";
|
||||
public class Role_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Throttles room stats and role updating to happen at most once every 512 or so ticks per room, instead of every time anything at all changes or moves";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Role));
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
{
|
||||
var this_statsAndRoleDirty = FishTranspiler.Field(typeof(Room), nameof(Room.statsAndRoleDirty));
|
||||
|
||||
try
|
||||
{
|
||||
return codes.ReplaceAt((codes, i)
|
||||
=> i - 1 > 0 && codes[i - 1] == this_statsAndRoleDirty
|
||||
&& codes[i].opcode == OpCodes.Brfalse_S && codes[i].operand is Label,
|
||||
code => new[]
|
||||
{
|
||||
code,
|
||||
FishTranspiler.This,
|
||||
FishTranspiler.Call(ShouldUpdate),
|
||||
FishTranspiler.IfFalse_Short((Label)code.operand)
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish failed to patch Room.Role. This just means that specific patch will be doing nothing.\n{ex}");
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
public static bool ShouldUpdate(Room __instance)
|
||||
{
|
||||
if (__instance.role is null || !RoomThrottleInfo.TryGetValue(__instance, out var value) || value < Current.Game.tickManager.TicksGame)
|
||||
{
|
||||
RoomThrottleInfo[__instance] = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static Dictionary<Room, int> RoomThrottleInfo { get; } = new();
|
||||
static Role_Patch() => Cache.Utility.All.Add(RoomThrottleInfo);
|
||||
}
|
||||
|
||||
public class Owners_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches room owner data and throttles them to only update at most once every ~512 or so ticks. RimWorld calculates owners by checking ownership of all items contained in a room, which can get quite expensive";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Owners));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Room __instance, ref IEnumerable<Pawn> __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref RoomOwnersCache.Get.GetReference(__instance);
|
||||
|
||||
if (cache.ShouldRefreshNow)
|
||||
{
|
||||
if (__instance.TouchesMapEdge || __instance.IsHuge || (__instance.statsAndRoleDirty && !__instance.ContainedBeds.Any()))
|
||||
{
|
||||
cache.ShouldRefreshNow = false;
|
||||
cache.owners = Array.Empty<Pawn>();
|
||||
RoomOwnersCache.Get[__instance] = cache;
|
||||
__result = cache.owners;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
__result = cache.owners;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Room __instance, bool __state, ref IEnumerable<Pawn> __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
if (RoomOwnersCache.Get.TryGetValue(__instance, out var cache) && cache.owners is List<Pawn> list)
|
||||
{
|
||||
list.Clear();
|
||||
foreach (var pawn in __result)
|
||||
list.Add(pawn);
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.owners = new List<Pawn>(__result);
|
||||
}
|
||||
|
||||
cache.ShouldRefreshNow = false;
|
||||
RoomOwnersCache.Get[__instance] = cache;
|
||||
__result = cache.owners;
|
||||
}
|
||||
|
||||
public struct FishRoomOwnersInfo
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public IEnumerable<Pawn> owners;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Regions_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Minor optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.Regions));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Regions_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Region> Regions_Replacement(Room instance)
|
||||
{
|
||||
var tmpRegions = instance.tmpRegions;
|
||||
var districts = instance.districts;
|
||||
lock (_lock)
|
||||
{
|
||||
tmpRegions.Clear();
|
||||
for (var i = 0; i < districts.Count; i++)
|
||||
tmpRegions.AddRange(districts[i].Regions);
|
||||
}
|
||||
return tmpRegions;
|
||||
}
|
||||
|
||||
private static readonly object _lock = new();
|
||||
}
|
||||
|
||||
public class ContainedBeds_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "So RimWorld has a list of beds and a list of all things. For some reason ludeon chose to have ContainedBeds retrieve its beds by looping through all things. This fixes that. Helps a lot against spikes";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedBeds));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ContainedBeds_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IEnumerable<Building_Bed> ContainedBeds_Replacement(Room instance)
|
||||
{
|
||||
var regions = instance.Regions;
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var beds = regions[i].ListerThings.listsByGroup[(int)ThingRequestGroup.Bed];
|
||||
if (beds is null)
|
||||
continue;
|
||||
for (var j = 0; j < beds.Count; j++)
|
||||
yield return (Building_Bed)beds[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ContainedAndAdjacentThings_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caching of room content info. Variable impact";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedAndAdjacentThings));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(ContainedAndAdjacentThings_Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Thing> ContainedAndAdjacentThings_Replacement(Room instance)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
uniqueContainedThings.Clear();
|
||||
|
||||
var regions = instance.Regions;
|
||||
|
||||
if (CachedThingLists.TryGetValue(instance, out var cache) && cache.listVersions.Count == regions.Count && !cache.ShouldRefreshNow)
|
||||
{
|
||||
var cacheIsAccurate = true;
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var allThings = regions[i].listerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
if (cache.listVersions[i] != -1)
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
else if (cache.listVersions[i] != allThings.Version())
|
||||
{
|
||||
cacheIsAccurate = false;
|
||||
}
|
||||
}
|
||||
if (cacheIsAccurate)
|
||||
{
|
||||
uniqueContainedThings.AddRange(cache.things);
|
||||
return uniqueContainedThings;
|
||||
}
|
||||
}
|
||||
|
||||
return RefreshContainedAndAdjacentThings(instance, regions, cache);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Thing> RefreshContainedAndAdjacentThings(Room instance, List<Region> regions, FishThingsInfo cache)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
var uniqueContainedThingsSet = instance.uniqueContainedThingsSet;
|
||||
uniqueContainedThingsSet.Clear();
|
||||
(cache.listVersions ??= new()).Clear();
|
||||
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var allThings = regions[i].ListerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
cache.listVersions.Add(-1);
|
||||
continue;
|
||||
}
|
||||
var count = allThings.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
uniqueContainedThingsSet.Add(allThings[j]);
|
||||
|
||||
cache.listVersions.Add(allThings.Version());
|
||||
}
|
||||
uniqueContainedThings.AddRange(uniqueContainedThingsSet);
|
||||
if (cache.things is List<Thing> list)
|
||||
list.ReplaceContentsWith(uniqueContainedThings);
|
||||
else
|
||||
cache.things = new List<Thing>(uniqueContainedThings);
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
CachedThingLists[instance] = cache;
|
||||
|
||||
uniqueContainedThingsSet.Clear();
|
||||
return uniqueContainedThings;
|
||||
}
|
||||
|
||||
/*private static List<Thing> RefreshContainedAndAdjacentThingsAlternative(Room instance, List<Region> regions, FishThingsInfo cache)
|
||||
{
|
||||
var uniqueContainedThings = instance.uniqueContainedThings;
|
||||
(cache.listVersions ??= new()).Clear();
|
||||
var map = instance.Map;
|
||||
var regionGrid = map.regionGrid.regionGrid;
|
||||
var mapSizeX = map.cellIndices.mapSizeX;
|
||||
|
||||
var adjacentThings = new List<Thing>();
|
||||
|
||||
for (var i = 0; i < regions.Count; i++)
|
||||
{
|
||||
var region = regions[i];
|
||||
var allThings = region.ListerThings.AllThings;
|
||||
if (allThings == null)
|
||||
{
|
||||
cache.listVersions.Add(-1);
|
||||
continue;
|
||||
}
|
||||
var count = allThings.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (regionGrid[CellIndicesUtility.CellToIndex(allThings[j].Position, mapSizeX)] == region)
|
||||
uniqueContainedThings.Add(allThings[j]);
|
||||
else
|
||||
adjacentThings.Add(allThings[j]);
|
||||
}
|
||||
|
||||
cache.listVersions.Add(Utility<Thing>.getListVersion(allThings));
|
||||
}
|
||||
|
||||
for (var i = 0; i < adjacentThings.Count; i++)
|
||||
{
|
||||
if (!uniqueContainedThings.Contains(adjacentThings[i]))
|
||||
uniqueContainedThings.Add(adjacentThings[i]);
|
||||
}
|
||||
|
||||
if (cache.things is List<Thing> list)
|
||||
list.ReplaceContentsWith(uniqueContainedThings);
|
||||
else
|
||||
cache.things = new List<Thing>(uniqueContainedThings);
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
CachedThingLists[instance] = cache;
|
||||
|
||||
return uniqueContainedThings;
|
||||
}*/
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static Dictionary<Room, FishThingsInfo> CachedThingLists { get; } = new();
|
||||
static ContainedAndAdjacentThings_Patch() => Cache.Utility.All.Add(CachedThingLists);
|
||||
|
||||
public struct FishThingsInfo
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public List<int> listVersions;
|
||||
public IEnumerable<Thing> things;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RoomAt_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Minor optimization through inlining ond cleaning up of duplicate calls in the method. About 1/4 faster, which isn't much, but it likely won't break anything either.";
|
||||
|
||||
public override Delegate? TargetMethodGroup => RegionAndRoomQuery.RoomAt;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(RoomAt);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All)
|
||||
{
|
||||
var mapSize = map.info.Size;
|
||||
if ((uint)c.x >= mapSize.x || (uint)c.z >= mapSize.z)
|
||||
return null;
|
||||
|
||||
var regionAndRoomUpdater = map.regionAndRoomUpdater;
|
||||
if (regionAndRoomUpdater.Enabled)
|
||||
regionAndRoomUpdater.TryRebuildDirtyRegionsAndRooms();
|
||||
else if (regionAndRoomUpdater.AnythingToRebuild)
|
||||
Log.Warning($"Trying to get valid region at {c} but RegionAndRoomUpdater is disabled. The result may be incorrect.");
|
||||
|
||||
var region = map.regionGrid.regionGrid[(c.z * mapSize.x) + c.x];
|
||||
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0 ? null
|
||||
: region.District?.Room;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class StoreUtilityPatches : ClassWithFishPatches
|
||||
{
|
||||
public class IsInValidBestStorage_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Patches IsInValidBestStorage to immediately remove things from haulables when they're found to be in the best storage. Default often has them checked several more times before removal happens";
|
||||
public override Delegate TargetMethodGroup => StoreUtility.IsInValidBestStorage;
|
||||
public static void Postfix(Thing t, bool __result)
|
||||
{
|
||||
if (__result)
|
||||
{
|
||||
t.Map.listerHaulables.TryRemove(t);
|
||||
}
|
||||
/*else if (!t.IsInValidStorage())
|
||||
{
|
||||
if (!StoreUtility.TryFindBestBetterStorageFor(t, null, t.Map, StoragePriority.Unstored, Faction.OfPlayerSilentFail, out IntVec3 _, out IHaulDestination _, needAccurateResult: false))
|
||||
{
|
||||
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
#if threadingEnabled
|
||||
public class IsGoodStoreCell_Patch : FishPatch
|
||||
{
|
||||
protected IsGoodStoreCell_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Delegate TargetMethodGroup => StoreUtility.IsGoodStoreCell;
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Enter(Lock);
|
||||
}
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Exit(Lock);
|
||||
return __exception;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
|
||||
public class GetStatValue_Patch : FishPatch
|
||||
{
|
||||
protected GetStatValue_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Delegate TargetMethodGroup => StatExtension.GetStatValue;
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Enter(Lock);
|
||||
}
|
||||
public static Exception Finalizer(Exception __exception)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
Monitor.Exit(Lock);
|
||||
return __exception;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.System;
|
||||
public class EqualityComparerOptimization : ClassWithFishPatches, IHasDescription
|
||||
{
|
||||
public string Description => "Specialized EqualityComparers for slightly faster collection lookups on ValueTypes. No settings for this one. Can reduce memory usage by preventing unnecessary boxing into object.";
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
foreach (var type in AccessTools.AllTypes())
|
||||
{
|
||||
if (type.IsValueType
|
||||
&& type != typeof(void)
|
||||
&& typeof(IEquatable<>).MakeGenericType(type).IsAssignableFrom(type)
|
||||
&& type != typeof(int)
|
||||
&& type != typeof(ushort)
|
||||
&& !type.IsGenericTypeDefinition)
|
||||
{
|
||||
SetValueForDefaultComparer(type, Activator.CreateInstance(typeof(EquatableValueTypeEqualityComparer<>).MakeGenericType(type)));
|
||||
}
|
||||
}
|
||||
|
||||
SetValueForDefaultComparer(typeof(int), new IntEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(ushort), new UshortEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(string), new StringEqualityComparer());
|
||||
|
||||
// below aren't IEquatable, so why don't we just go fix that
|
||||
SetValueForDefaultComparer(typeof(IntPtr), new IntPtrEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(RuntimeFieldHandle), new RuntimeFieldHandleEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(RuntimeTypeHandle), new RuntimeTypeHandleEqualityComparer());
|
||||
SetValueForDefaultComparer(typeof(RuntimeMethodHandle), new RuntimeMethodHandleEqualityComparer());
|
||||
}
|
||||
|
||||
private static void SetValueForDefaultComparer(Type type, object value)
|
||||
=> typeof(EqualityComparer<>).MakeGenericType(type).GetField("defaultComparer", AccessTools.allDeclared).SetValue(null, value);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class EquatableValueTypeEqualityComparer<T> : EqualityComparer<T> where T : struct, IEquatable<T>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(T x, T y) => x.Equals(y);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(T obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is EquatableValueTypeEqualityComparer<T>;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class StringEqualityComparer : EqualityComparer<string>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(string x, string y) => string.Equals(x, y); // already does a nullcheck, so this skips the extra check that'd otherwise happen
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(string obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is StringEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IntPtrEqualityComparer : EqualityComparer<IntPtr>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(IntPtr x, IntPtr y) => x == y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(IntPtr obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is IntPtrEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeFieldHandleEqualityComparer : EqualityComparer<RuntimeFieldHandle>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(RuntimeFieldHandle x, RuntimeFieldHandle y) => x.Value == y.Value;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(RuntimeFieldHandle obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is RuntimeFieldHandleEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeTypeHandleEqualityComparer : EqualityComparer<RuntimeTypeHandle>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(RuntimeTypeHandle x, RuntimeTypeHandle y) => x.Value == y.Value;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(RuntimeTypeHandle obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is RuntimeTypeHandleEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeMethodHandleEqualityComparer : EqualityComparer<RuntimeMethodHandle>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(RuntimeMethodHandle x, RuntimeMethodHandle y) => x.Value == y.Value;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(RuntimeMethodHandle obj) => obj.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is RuntimeMethodHandleEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class IntEqualityComparer : EqualityComparer<int>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(int x, int y) => x == y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(int obj) => obj;
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is IntEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UshortEqualityComparer : EqualityComparer<ushort>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(ushort x, ushort y) => x == y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(ushort obj) => obj;
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is UshortEqualityComparer;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ReferenceEqualityComparer<T> : EqualityComparer<T>
|
||||
{
|
||||
public static new readonly ReferenceEqualityComparer<T> Default = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(T x, T y) => ReferenceEquals(x, y);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Equals method for the comparer itself.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj) => obj is ReferenceEqualityComparer<T>;
|
||||
public override int GetHashCode() => GetType().Name.GetHashCode();
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
using ConstructorInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.ConstructorInfo>;
|
||||
using FieldGetters = PerformanceFish.Cache.ByReference<System.RuntimeFieldHandle, PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoGetterCache>;
|
||||
using FieldInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.FieldInfo>;
|
||||
using FieldSetters = PerformanceFish.Cache.ByReference<System.RuntimeFieldHandle, PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoSetterCache>;
|
||||
using MethodInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.MethodInfo>;
|
||||
using MethodInfoWithTypesAndFlagsCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.MethodInfo>;
|
||||
using MethodInfoWithTypesCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, string, PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, System.Reflection.MethodInfo>;
|
||||
using MethodInvokers = PerformanceFish.Cache.ByReference<System.RuntimeMethodHandle, PerformanceFish.System.ReflectionCaching.MethodBasePatches.MethodInvokerCache>;
|
||||
using PropertyInfoCache = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, System.Reflection.PropertyInfo>;
|
||||
|
||||
namespace PerformanceFish.System;
|
||||
public class ReflectionCaching : ClassWithFishPatches
|
||||
{
|
||||
public static void Initialize() // necessary to prevent recursion between patches and function pointer creation
|
||||
{
|
||||
try
|
||||
{
|
||||
ConstructorInfoCache.Initialize();
|
||||
FieldGetters.Initialize();
|
||||
FieldInfoCache.Initialize();
|
||||
FieldSetters.Initialize();
|
||||
MethodInfoCache.Initialize();
|
||||
MethodInfoWithTypesAndFlagsCache.Initialize();
|
||||
MethodInfoWithTypesCache.Initialize();
|
||||
MethodInvokers.Initialize();
|
||||
PropertyInfoCache.Initialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize its ReflectionCaching:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public struct StateAndFlags
|
||||
{
|
||||
public bool State;
|
||||
public BindingFlags Flags;
|
||||
}
|
||||
|
||||
public class TypePatches
|
||||
{
|
||||
public class GetField_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetField lookups";
|
||||
public override MethodBase TargetMethodInfo
|
||||
=> Reflection.MethodInfo("mscorlib", "System.RuntimeType", nameof(Type.GetField), new[] { typeof(string), typeof(BindingFlags) });
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref FieldInfo? __result, out StateAndFlags __state)
|
||||
{
|
||||
var key = new FieldInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
|
||||
|
||||
__result = FieldInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
if (__result is null)
|
||||
{
|
||||
__state = new() { State = true, Flags = bindingAttr };
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
__state = new() { State = false, Flags = bindingAttr };
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, FieldInfo __result, StateAndFlags __state)
|
||||
{
|
||||
if (!__state.State || __result is null)
|
||||
return;
|
||||
|
||||
FieldInfoCache.Get[new(__instance.TypeHandle, __state.Flags, ToLowerIfNeededForBindingFlags(name, __state.Flags))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetMethodWithFlags_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetMethod lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, default(BindingFlags));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref MethodInfo? __result, out bool __state)
|
||||
{
|
||||
var key = new MethodInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
|
||||
|
||||
__result = MethodInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, BindingFlags bindingAttr, MethodInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
MethodInfoCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetMethodWithTypes_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetMethod lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, Type[] types, ref MethodInfo? __result, out bool __state)
|
||||
{
|
||||
var key = new MethodInfoWithTypesCache(__instance.TypeHandle, name, new(types));
|
||||
|
||||
__result = MethodInfoWithTypesCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, Type[] types, MethodInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
MethodInfoWithTypesCache.Get[new(__instance.TypeHandle, name, new(types))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetMethodWithFlagsAndTypes_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetMethod lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetMethod(null, default, null, null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers, ref MethodInfo? __result, out bool __state)
|
||||
{
|
||||
if (binder != null || modifiers is { Length: > 0 })
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new MethodInfoWithTypesAndFlagsCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr), new(types));
|
||||
|
||||
__result = MethodInfoWithTypesAndFlagsCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, Type[] types, BindingFlags bindingAttr, MethodInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
MethodInfoWithTypesAndFlagsCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr), new(types))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetConstructor_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetConstructor lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetConstructor(default, null, null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers, ref ConstructorInfo? __result, out bool __state)
|
||||
{
|
||||
if (binder != null || modifiers is { Length: > 0 })
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new ConstructorInfoCache(__instance.TypeHandle, bindingAttr, new(types));
|
||||
|
||||
__result = ConstructorInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, BindingFlags bindingAttr, Type[] types, ConstructorInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
ConstructorInfoCache.Get[new(__instance.TypeHandle, bindingAttr, new(types))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetProperty_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches GetProperty lookups";
|
||||
public override Expression<Action> TargetMethod => () => default(Type)!.GetProperty(null, default(BindingFlags));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Type __instance, string name, BindingFlags bindingAttr, ref PropertyInfo? __result, out bool __state)
|
||||
{
|
||||
var key = new PropertyInfoCache(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr));
|
||||
|
||||
__result = PropertyInfoCache.Get.TryGetValue(ref key);
|
||||
|
||||
return __state = __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Type __instance, string name, BindingFlags bindingAttr, PropertyInfo __result, bool __state)
|
||||
{
|
||||
if (!__state || __result is null)
|
||||
return;
|
||||
|
||||
PropertyInfoCache.Get[new(__instance.TypeHandle, bindingAttr, ToLowerIfNeededForBindingFlags(name, bindingAttr))] = __result;
|
||||
}
|
||||
}
|
||||
|
||||
// probably never actually needed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string ToLowerIfNeededForBindingFlags(string name, BindingFlags bindingAttr) => (bindingAttr & BindingFlags.IgnoreCase) != 0 ? name.ToLowerInvariant() : name;
|
||||
}
|
||||
|
||||
public struct ByValueComparableArray<T> : IEquatable<ByValueComparableArray<T>>
|
||||
where T : class
|
||||
{
|
||||
public T[] Array;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByValueComparableArray(T[] array)
|
||||
{
|
||||
Guard.IsNotNull(array);
|
||||
Array = array;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByValueComparableArray<T> other)
|
||||
{
|
||||
var length = Array.Length;
|
||||
if (length != other.Array.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (!Array[i].Equals(other.Array[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj) => obj is ByValueComparableArray<T> array && Equals(array);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(Array.Length, Array.Length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(ByValueComparableArray<T> left, ByValueComparableArray<T> right) => left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(ByValueComparableArray<T> left, ByValueComparableArray<T> right) => !(left == right);
|
||||
}
|
||||
|
||||
#if eee == false
|
||||
public class FieldInfoPatches
|
||||
{
|
||||
public class GetValue_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes the GetValue method by invoking it through specialized cached delegates";
|
||||
|
||||
//public GetValue_Patch()
|
||||
//{
|
||||
// var types = AllSubclassesNonAbstract(typeof(FieldInfo))
|
||||
// .SelectMany(t
|
||||
// => DeclaredMethodOrNothing(t, nameof(FieldInfo.GetValue), new[] { typeof(object) }))
|
||||
// .Select(m => m.DeclaringType);
|
||||
// Log.Message(types.ToStringSafeEnumerable());
|
||||
//}
|
||||
|
||||
public override MethodBase TargetMethodInfo => Reflection.MethodInfo("mscorlib", "System.Reflection.MonoField", nameof(FieldInfo.GetValue));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object obj, ref object __result)
|
||||
{
|
||||
ref var cache = ref FieldGetters.Get.TryGetReferenceUnsafe(__instance.FieldHandle);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
if (cache.func != null || UpdateGetterCache(ref cache, __instance))
|
||||
{
|
||||
__result = cache.func!(obj);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCache(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AddCache(FieldInfo __instance)
|
||||
{
|
||||
FieldGetters.Get.Add(__instance.FieldHandle, new());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SetValue_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes the SetValue method by invoking it through specialized cached delegates";
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(FieldInfo)!.SetValue(null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(FieldInfo __instance, object obj, object value)
|
||||
{
|
||||
ref var cache = ref FieldSetters.Get.TryGetReferenceUnsafe(__instance.FieldHandle);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
if (cache.action != null || UpdateSetterCache(ref cache, __instance))
|
||||
{
|
||||
cache.action!(obj, value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCache(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AddCache(FieldInfo __instance)
|
||||
{
|
||||
FieldSetters.Get.Add(__instance.FieldHandle, new());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UpdateGetterCache(ref FieldInfoGetterCache cache, FieldInfo instance)
|
||||
{
|
||||
cache.callCount++;
|
||||
|
||||
if (cache.callCount == 300)
|
||||
cache.func = MakeGetterDelegate(instance);
|
||||
else if (cache.callCount > 300)
|
||||
return false;
|
||||
|
||||
FieldGetters.Get[instance.FieldHandle] = cache;
|
||||
|
||||
return cache.func != null;
|
||||
}
|
||||
|
||||
public static bool UpdateSetterCache(ref FieldInfoSetterCache cache, FieldInfo instance)
|
||||
{
|
||||
cache.callCount++;
|
||||
|
||||
if (cache.callCount == 300)
|
||||
cache.action = MakeSetterDelegate(instance);
|
||||
else if (cache.callCount > 300)
|
||||
return false;
|
||||
|
||||
FieldSetters.Get[instance.FieldHandle] = cache;
|
||||
|
||||
return cache.action != null;
|
||||
}
|
||||
|
||||
private static FieldGetter? MakeGetterDelegate(FieldInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dm = new DynamicMethod($"FieldGetter_{info.Name}", typeof(object), new[] { typeof(object) }, info.DeclaringType, true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
il.Emit(FishTranspiler.This);
|
||||
|
||||
il.Emit(info.DeclaringType.IsValueType
|
||||
? FishTranspiler.Unbox(info.DeclaringType)
|
||||
: FishTranspiler.Cast(info.DeclaringType));
|
||||
}
|
||||
|
||||
il.Emit(
|
||||
/*info.DeclaringType.IsEnum
|
||||
&& info.DeclaringType.GetEnumUnderlyingType() is var enumType
|
||||
? enumType == typeof(long) || enumType == typeof(ulong)
|
||||
? FishTranspiler.Constant((long)info.GetValue(info.DeclaringType))
|
||||
: FishTranspiler.Constant((int)info.GetValue(info.DeclaringType))
|
||||
:*/ info.IsLiteral && !info.IsInitOnly ? FishTranspiler.Constant(info.GetRawConstantValue())
|
||||
: FishTranspiler.Field(info));
|
||||
|
||||
if (info.FieldType.IsValueType)
|
||||
il.Emit(FishTranspiler.Box(info.FieldType));
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (FieldGetter)dm.CreateDelegate(typeof(FieldGetter));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"PerformanceFish failed to generate an optimized delegate for {GetFieldDescription(info)}. Reverting to default behaviour instead.\n{ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FieldSetter? MakeSetterDelegate(FieldInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dm = new DynamicMethod($"FieldSetter_{info.Name}", typeof(void), new[] { typeof(object), typeof(object) }, info.DeclaringType, true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
il.Emit(FishTranspiler.This);
|
||||
|
||||
il.Emit(info.DeclaringType.IsValueType
|
||||
? FishTranspiler.Unbox(info.DeclaringType)
|
||||
: FishTranspiler.Cast(info.DeclaringType));
|
||||
}
|
||||
|
||||
il.Emit(FishTranspiler.Argument(1));
|
||||
|
||||
if (info.FieldType.IsValueType)
|
||||
{
|
||||
//il.Emit(FishTranspiler.UnboxAny(info.FieldType));
|
||||
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), info.FieldType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
|
||||
new[] { typeof(object) },
|
||||
new[] { info.FieldType.IsNullable() ? Nullable.GetUnderlyingType(info.FieldType) : info.FieldType }));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(FishTranspiler.Cast(info.FieldType));
|
||||
}
|
||||
|
||||
il.Emit(FishTranspiler.StoreField(info));
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (FieldSetter)dm.CreateDelegate(typeof(FieldSetter));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"PerformanceFish failed to generate an optimized delegate for {GetFieldDescription(info)}. Reverting to default behaviour instead.\n{ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct FieldInfoGetterCache
|
||||
{
|
||||
public FieldGetter? func;
|
||||
public int callCount;
|
||||
}
|
||||
|
||||
public struct FieldInfoSetterCache
|
||||
{
|
||||
public FieldSetter? action;
|
||||
public int callCount;
|
||||
}
|
||||
|
||||
public delegate object FieldGetter(object obj);
|
||||
|
||||
public delegate void FieldSetter(object obj, object value);
|
||||
}
|
||||
|
||||
private static string GetFieldDescription(FieldInfo info) => $"{(info.IsPublic ? "public" : "private")} {(info.IsLiteral && !info.IsInitOnly ? "const " : info.IsStatic ? "static " : "")} {info.FieldType.FullDescription()} {info.DeclaringType.FullDescription()}:{info.Name}";
|
||||
|
||||
public class MethodBasePatches
|
||||
{
|
||||
public class Invoke_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimizes the Invoke method by invoking it through specialized cached delegates";
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(MethodBase)!.Invoke(null, null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(MethodBase __instance, object obj, object[] parameters, ref object __result)
|
||||
{
|
||||
if (__instance is not MethodInfo method)
|
||||
return true;
|
||||
|
||||
ref var cache = ref MethodInvokers.Get.TryGetReferenceUnsafe(method.MethodHandle);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
if (cache.func != null || UpdateCache(ref cache, method))
|
||||
{
|
||||
__result = cache.func!(obj, parameters);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AddCache(method);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AddCache(MethodInfo method)
|
||||
{
|
||||
MethodInvokers.Get.Add(method.MethodHandle, new());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UpdateCache(ref MethodInvokerCache cache, MethodInfo instance)
|
||||
{
|
||||
cache.callCount++;
|
||||
if (cache.callCount == 300)
|
||||
cache.func = MakeInvokeDelegate(instance);
|
||||
else if (cache.callCount > 300)
|
||||
return false;
|
||||
|
||||
MethodInvokers.Get[instance.MethodHandle] = cache;
|
||||
|
||||
return cache.func != null;
|
||||
}
|
||||
|
||||
// HarmonyLib.MethodInvoker can't handle arrays with null values when used with ByRef value types. That aside this is near identical.
|
||||
public static MethodInvoker? MakeInvokeDelegate(MethodInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
//DebugLog.Message($"Making delegate for {(info.IsStatic ? "static" : "")} {info.DeclaringType} {info} with arguments {info.GetParameters().ToStringSafeEnumerable()}");
|
||||
|
||||
var dm = new DynamicMethod($"MethodInvoker_{info.Name}", typeof(object), new[] { typeof(object), typeof(object[]) }, info.DeclaringType, true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
var parameters = info.GetParameters();
|
||||
|
||||
if (!info.IsStatic)
|
||||
{
|
||||
il.Emit(FishTranspiler.This);
|
||||
if (info.DeclaringType.IsValueType)
|
||||
il.Emit(FishTranspiler.UnboxAny(info.DeclaringType));
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
il.Emit(FishTranspiler.Argument(1));
|
||||
il.Emit(FishTranspiler.Constant(i));
|
||||
|
||||
var parameterType = parameters[i].ParameterType;
|
||||
|
||||
if (parameterType.IsByRef)
|
||||
{
|
||||
parameterType = parameterType.GetElementType();
|
||||
|
||||
il.Emit(FishTranspiler.LoadElementAddress<object>());
|
||||
|
||||
il.Emit(parameterType.IsValueType
|
||||
? FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
|
||||
new[] { typeof(object).MakeByRefType() },
|
||||
new[] { parameterType.IsNullable() ? Nullable.GetUnderlyingType(parameterType) : parameterType })
|
||||
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely),
|
||||
new[] { typeof(object).MakeByRefType() },
|
||||
new[] { typeof(object), parameterType }));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(FishTranspiler.LoadElement<object>());
|
||||
|
||||
il.Emit(parameterType.IsValueType
|
||||
? FishTranspiler.Call(typeof(ReflectionCaching), parameterType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
|
||||
new[] { typeof(object) },
|
||||
new[] { parameterType.IsNullable() ? Nullable.GetUnderlyingType(parameterType) : parameterType })
|
||||
: FishTranspiler.As(parameterType));
|
||||
}
|
||||
}
|
||||
|
||||
il.Emit(FishTranspiler.Call(info));
|
||||
|
||||
if (info.ReturnType == typeof(void))
|
||||
il.Emit(FishTranspiler.Null);
|
||||
else if (info.ReturnType.IsValueType)
|
||||
il.Emit(FishTranspiler.Box(info.ReturnType));
|
||||
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (MethodInvoker)dm.CreateDelegate(typeof(MethodInvoker));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"PerformanceFish failed to generate an optimized delegate for {info.FullDescription()}. Reverting to default behaviour instead.\n{ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public struct MethodInvokerCache
|
||||
{
|
||||
public MethodInvoker? func;
|
||||
public int callCount;
|
||||
}
|
||||
|
||||
public delegate object MethodInvoker(object obj, object[] parameters);
|
||||
}
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TTo CastRefSafely<TFrom, TTo>(ref TFrom from)
|
||||
=> ref from is TTo or null
|
||||
? ref Unsafe.As<TFrom, TTo>(ref from)
|
||||
: ref ThrowInvalidCastException<TTo>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TTo UnboxRefSafely<TTo>(ref object from) where TTo : struct
|
||||
=> ref from is TTo
|
||||
? ref Unsafe.Unbox<TTo>(from)
|
||||
: ref Unsafe.Unbox<TTo>(from
|
||||
= from is null
|
||||
? default
|
||||
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TTo UnboxSafely<TTo>(object from) where TTo : struct
|
||||
=> from is TTo to
|
||||
? to
|
||||
// : from is IConvertible
|
||||
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TTo? UnboxNullableSafely<TTo>(object from) where TTo : struct
|
||||
=> from is TTo to
|
||||
? to
|
||||
: from is null
|
||||
? null
|
||||
// : from is IConvertible
|
||||
: FisheryLib.Convert.To<IConvertible, TTo>((IConvertible)from);
|
||||
|
||||
[DoesNotReturn]
|
||||
private static ref T ThrowInvalidCastException<T>() => throw new InvalidCastException();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using IdeoForbidsCache = PerformanceFish.Cache.ByIntRefreshable<RimWorld.CompAssignableToPawn_Bed, Verse.Pawn, PerformanceFish.ThingCompCaching.IdeoForbidsCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class ThingCompCaching : ClassWithFishPatches
|
||||
{
|
||||
public class CompAssignableToPawn_Bed_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caches results of CompAssignableToPawn_Bed.IdeoligionForbids";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(CompAssignableToPawn_Bed), nameof(CompAssignableToPawn_Bed.IdeoligionForbids));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(CompAssignableToPawn_Bed __instance, Pawn pawn, ref bool __result, out bool __state)
|
||||
{
|
||||
if (pawn is null)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new IdeoForbidsCache(__instance.parent.thingIDNumber, pawn.thingIDNumber);
|
||||
ref var cache = ref IdeoForbidsCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Result;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(CompAssignableToPawn_Bed __instance, Pawn pawn, bool __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
IdeoForbidsCache.Get[new(__instance, pawn)] = new(__instance, __result, pawn);
|
||||
}
|
||||
}
|
||||
|
||||
public struct IdeoForbidsCacheValue : Cache.IIsRefreshable<IdeoForbidsCache, IdeoForbidsCacheValue>
|
||||
{
|
||||
public bool Result;
|
||||
private int _nextRefreshTick;
|
||||
private int _assignedPawnsVersion;
|
||||
private int _directRelationsVersion;
|
||||
private List<Pawn> _assignedPawns;
|
||||
private List<DirectPawnRelation> _directRelations;
|
||||
|
||||
public IdeoForbidsCacheValue(CompAssignableToPawn_Bed comp, bool result, Pawn pawn)
|
||||
{
|
||||
_directRelations = pawn.relations.DirectRelations;
|
||||
_assignedPawns = comp.assignedPawns;
|
||||
_directRelationsVersion = _directRelations.Version();
|
||||
Result = result;
|
||||
_assignedPawnsVersion = _assignedPawns.Version();
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 1024 + Math.Abs(Rand.Int % 512);
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get
|
||||
=> _nextRefreshTick < Current.Game.tickManager.TicksGame
|
||||
|| _assignedPawnsVersion != _assignedPawns.Version()
|
||||
|| _directRelationsVersion != _directRelations.Version();
|
||||
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 2048 + Math.Abs(Rand.Int % 1024);
|
||||
}
|
||||
|
||||
public IdeoForbidsCacheValue SetNewValue(IdeoForbidsCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
global using AllowedDefsListCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.ThingFilter, PerformanceFish.ThingFilterPatches.AllowedDefsListCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class ThingFilterPatches : ClassWithFishPatches
|
||||
{
|
||||
public override bool RequiresLoadedGameForPatching => true;
|
||||
/*public class Allows_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ThingFilter().Allows(null as Thing);
|
||||
public static bool Prefix(ThingFilter __instance, ref bool __result, Thing t, out bool __state)
|
||||
{
|
||||
if (!Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>.TryGetValue(t, __instance, out var cache))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.allows;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(ThingFilter __instance, bool __result, Thing t, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
ThingFilterAllowsCache[new(t, __instance)] = new() { ShouldRefreshNow = false, allows = __result };
|
||||
}
|
||||
public static Dictionary<Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>, FishThingFilterInfo> ThingFilterAllowsCache => Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>.Get;
|
||||
public struct FishThingFilterInfo : ICanRefresh<Cache.ByReferenceRefreshable<Thing, ThingFilter, FishThingFilterInfo>, FishThingFilterInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool allows;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public FishThingFilterInfo SetNewValue(Cache<Thing, ThingFilter, FishThingFilterInfo> key) => throw new NotImplementedException();
|
||||
}
|
||||
}*/
|
||||
|
||||
public class AllowedThingDefs_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Small optimization. Returns a list instead of hashset for better performance when enumerating";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingFilter), nameof(ThingFilter.AllowedThingDefs));
|
||||
public static IEnumerable<ThingDef> Replacement(ThingFilter instance) => AllowedDefsListCache.GetValue(instance).defs;
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes) => Reflection.GetCodeInstructions(Replacement);
|
||||
}
|
||||
|
||||
public class AllStorableThingDefs_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Small optimization";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingFilter), nameof(ThingFilter.AllStorableThingDefs));
|
||||
public static IEnumerable<ThingDef> Replacement()
|
||||
{
|
||||
var allDefs = DefDatabase<ThingDef>.AllDefsListForReading;
|
||||
var count = allDefs.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (allDefs[i].EverStorable(true))
|
||||
yield return allDefs[i];
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(Replacement);
|
||||
}
|
||||
|
||||
public class CopyAllowancesFrom_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.CopyAllowancesFrom(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Prefix(ThingFilter __instance) => AllowedDefsListCache.GetValue(__instance).defs.Clear();
|
||||
}
|
||||
|
||||
public class SetAllow_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.SetAllow(default(ThingDef), default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Prefix(ThingFilter __instance, ThingDef thingDef, bool allow)
|
||||
{
|
||||
if (__instance.allowedDefs is null)
|
||||
return;
|
||||
|
||||
ref var cache = ref AllowedDefsListCache.GetValue(__instance);
|
||||
|
||||
if (allow)
|
||||
{
|
||||
if (!__instance.allowedDefs.Contains(thingDef))
|
||||
{
|
||||
var index = ~Array.BinarySearch(cache.defs._items, 0, cache.defs.Count, thingDef, Comparer);
|
||||
//var index = ~cache.defs.BinarySearch(thingDef, Comparer);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
cache.defs.Insert(index, thingDef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (__instance.allowedDefs.Contains(thingDef))
|
||||
cache.defs.Remove(thingDef);
|
||||
}
|
||||
}
|
||||
|
||||
private static Comparer<ThingDef> Comparer { get; set; }
|
||||
= Comparer<ThingDef>.Create(ComparerMethod);
|
||||
|
||||
private static int ComparerMethod(ThingDef a, ThingDef b)
|
||||
=> a.BaseMarketValue.CompareTo(b.BaseMarketValue);
|
||||
}
|
||||
|
||||
public class SetDisallowAll_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.SetDisallowAll(null, null);
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingFilter __instance) => ForceSynchronizeCache(__instance);
|
||||
}
|
||||
|
||||
public class SetAllowAll_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used by the WorkGiver_DoBill patch to allow for faster refreshing of its cache";
|
||||
public override Expression<Action> TargetMethod => () => default(ThingFilter)!.SetAllowAll(null, default);
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingFilter __instance) => ForceSynchronizeCache(__instance);
|
||||
}
|
||||
|
||||
public static void ForceSynchronizeCache(ThingFilter filter) => AllowedDefsListCache.GetValueAndRefreshNow(filter);
|
||||
|
||||
public struct AllowedDefsListCacheValue : Cache.IIsRefreshable<ThingFilter, AllowedDefsListCacheValue>
|
||||
{
|
||||
public List<ThingDef> defs;
|
||||
private int _nextRefreshTick;
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 16384 + Math.Abs(Rand.Int % 4096);
|
||||
}
|
||||
public AllowedDefsListCacheValue SetNewValue(ThingFilter key)
|
||||
{
|
||||
var allowedDefs = key.allowedDefs;
|
||||
allowedDefs.Remove(null); // apparently happens
|
||||
|
||||
if (defs is null)
|
||||
defs = new(allowedDefs);
|
||||
else
|
||||
defs.ReplaceContentsWith(allowedDefs);
|
||||
|
||||
SortDefs(defs); // why, actually? I don't remember
|
||||
defs.Version()++;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void SortDefs(List<ThingDef> defs)
|
||||
{
|
||||
var defCount = defs.Count;
|
||||
var defArray = defs._items;
|
||||
var marketValues = GetTempArrayForSorting(defCount);
|
||||
for (var i = 0; i < defCount; i++)
|
||||
marketValues[i] = GetMarketValueSafely(defArray[i]);
|
||||
|
||||
//Array.Sort(marketValues, defs); would require an array of perfect length, generating garbage
|
||||
|
||||
defArray.AsSpan()[..defCount].Sort(marketValues.AsSpan()[..defCount], null);
|
||||
defs._version++;
|
||||
}
|
||||
|
||||
private static float[] GetTempArrayForSorting(int defCount) => _tempArrayForSorting.Length >= defCount ? _tempArrayForSorting : _tempArrayForSorting = new float[defCount];
|
||||
private static float[] _tempArrayForSorting = Array.Empty<float>();
|
||||
|
||||
private static float GetMarketValueSafely(ThingDef def)
|
||||
{
|
||||
try
|
||||
{
|
||||
return def.BaseMarketValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return LogAndFixMarketValue(def, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static float LogAndFixMarketValue(ThingDef def, Exception ex)
|
||||
{
|
||||
Log.Warning($"Exception thrown while calculating market value for def {def.defName} from mod {def.modContentPack?.Name ?? "null"}. Attempting to fix this now so it doesn't happen again.\n{ex}");
|
||||
var recipe = StatWorker_MarketValue.CalculableRecipe(def); //<-- this is slow, but gets cached by perf optimizer. Might be worth replicating
|
||||
|
||||
if (recipe?.ingredients is { } ingredients)
|
||||
ingredients.Remove(null);
|
||||
else if (def.CostList is { } costList)
|
||||
costList.Remove(null);
|
||||
|
||||
try
|
||||
{
|
||||
return def.BaseMarketValue;
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Log.Error($"Exception thrown while calculating market value for def {def.defName} from mod {def.modContentPack?.Name ?? "null"} again. Fix failed, F.\n{ex2}");
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using ThingRequestCache = PerformanceFish.Cache.ByReferenceRefreshable<int, byte, System.RuntimeTypeHandle, PerformanceFish.ThingRequestInfo>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class ThingOwnerUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GetAllThingsRecursively_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Caching of information about mostly unspawned pawns and things. Relatively large performance impact";
|
||||
public override Delegate TargetMethodGroup => ThingOwnerUtility.GetAllThingsRecursively<Thing>;
|
||||
|
||||
public static bool Prefix(Map map, ThingRequest request, IList outThings, bool allowUnreal, Predicate<IThingHolder> passCheck, bool alsoGetSpawnedThings, out bool __state)
|
||||
{
|
||||
if (request.singleDef != null || request.group == 0 || !allowUnreal || passCheck != null || alsoGetSpawnedThings || outThings == null)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
var listByGroup = map.listerThings.listsByGroup[(int)request.group];
|
||||
if (listByGroup is null)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var type = outThings.GetType().GetGenericArguments()[0];
|
||||
var cache = GetAndCheckCache(map, request.group, type);
|
||||
if (cache.count == listByGroup.Count)
|
||||
{
|
||||
if (ThingHolderCount(map) != cache.thingHolderCount)
|
||||
return __state = true;
|
||||
|
||||
outThings.Clear();
|
||||
outThings.TryAddRange(cache.things, type);
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
public static void Postfix(Map map, ThingRequest request, IList outThings, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
var type = outThings.GetType().GetGenericArguments()[0];
|
||||
var cache = ThingRequestCache.Get[new(map.uniqueID, (byte)request.group, type.TypeHandle)];
|
||||
if (cache.things is null)
|
||||
{
|
||||
cache.things = FisheryLib.CollectionExtensions.TryNew(outThings, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.things.Clear();
|
||||
cache.things.TryAddRange(outThings, type);
|
||||
}
|
||||
cache.ShouldRefreshNow = false;
|
||||
cache.count = map.listerThings.listsByGroup[(int)request.group]?.Count ?? -1;
|
||||
cache.thingHolderCount = ThingHolderCount(map);
|
||||
|
||||
ThingRequestCache.Get[new(map.uniqueID, (byte)request.group, type.TypeHandle)] = cache;
|
||||
}
|
||||
|
||||
public static ref ThingRequestInfo GetAndCheckCache(Map map, ThingRequestGroup group, Type type)
|
||||
{
|
||||
var key = new ThingRequestCache(map.uniqueID, (byte)group, type.TypeHandle);
|
||||
ref var cache = ref ThingRequestCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
ThingRequestCache.Get[key] = new();
|
||||
cache = ref ThingRequestCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
}
|
||||
|
||||
if (cache.ShouldRefreshNow)
|
||||
{
|
||||
cache.count = map.listerThings.listsByGroup[(int)group]?.Count ?? -1;
|
||||
cache.thingHolderCount = ThingHolderCount(map);
|
||||
cache.ShouldRefreshNow = false;
|
||||
ThingRequestCache.Get[new(map.uniqueID, (byte)group, type.TypeHandle)] = cache;
|
||||
cache.count = -1;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<ThingRequestInfo>(Unsafe.AsPointer(ref cache));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int ThingHolderCount(Map map) => (map.passingShipManager?.passingShips.Count ?? 0) + map.components.Count + (map.listerThings.listsByGroup[(int)ThingRequestGroup.ThingHolder]?.Count ?? 0);
|
||||
|
||||
/*public static void GetAllThingsRecursively(IThingHolder holder, List<Thing> outThings)
|
||||
{
|
||||
outThings.Clear();
|
||||
|
||||
ThingOwnerUtility.tmpStack.Clear();
|
||||
ThingOwnerUtility.tmpStack.Push(holder);
|
||||
while (ThingOwnerUtility.tmpStack.Count != 0)
|
||||
{
|
||||
IThingHolder thingHolder = ThingOwnerUtility.tmpStack.Pop();
|
||||
ThingOwner directlyHeldThings = thingHolder.GetDirectlyHeldThings();
|
||||
if (directlyHeldThings != null)
|
||||
{
|
||||
outThings.AddRange(directlyHeldThings);
|
||||
}
|
||||
|
||||
ThingOwnerUtility.tmpHolders.Clear();
|
||||
thingHolder.GetChildHolders(ThingOwnerUtility.tmpHolders);
|
||||
for (int i = 0; i < ThingOwnerUtility.tmpHolders.Count; i++)
|
||||
{
|
||||
ThingOwnerUtility.tmpStack.Push(ThingOwnerUtility.tmpHolders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ThingOwnerUtility.tmpStack.Clear();
|
||||
ThingOwnerUtility.tmpHolders.Clear();
|
||||
}
|
||||
|
||||
public static void AppendThingHoldersFromThings(List<IThingHolder> outThingsHolders, IList<Thing> container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (int count = container.Count; i < count; i++)
|
||||
{
|
||||
IThingHolder thingHolder = container[i] as IThingHolder;
|
||||
if (thingHolder != null)
|
||||
{
|
||||
outThingsHolders.Add(thingHolder);
|
||||
}
|
||||
|
||||
ThingWithComps thingWithComps = container[i] as ThingWithComps;
|
||||
if (thingWithComps == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ThingComp> allComps = thingWithComps.AllComps;
|
||||
for (int j = 0; j < allComps.Count; j++)
|
||||
{
|
||||
IThingHolder thingHolder2 = allComps[j] as IThingHolder;
|
||||
if (thingHolder2 != null)
|
||||
{
|
||||
outThingsHolders.Add(thingHolder2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public struct ThingRequestInfo : Cache.IIsRefreshable<ThingRequestCache, ThingRequestInfo>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public int count;
|
||||
public int thingHolderCount;
|
||||
public IList things;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public ThingRequestInfo SetNewValue(ThingRequestCache key) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using SharedAltarCache = PerformanceFish.Cache.ByIndex<RimWorld.Ideo, PerformanceFish.ThoughtWorker_Precept_AltarSharingOptimization.SharedAltarCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class ThoughtWorker_Precept_AltarSharingOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class SharedAltar_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of ideology's shared altar thoughtworker, throttles it to wait a minimum of 128 ticks between refreshes and makes refreshing happen async on a different thread. Quite impactful on performance";
|
||||
public override Expression<Action> TargetMethod => () => default(ThoughtWorker_Precept_AltarSharing)!.SharedAltar(null);
|
||||
public static void AsyncThoughtWorker(ThoughtWorker_Precept_AltarSharing __instance, Pawn pawn)
|
||||
{
|
||||
var allThings = pawn.Map.listerThings.listsByGroup[(int)ThingRequestGroup.BuildingArtificial].ToArray();
|
||||
Task.Run(() =>
|
||||
{
|
||||
var newThing = SharedAltarParallel(pawn, allThings);
|
||||
lock (Lock)
|
||||
SharedAltarCache.GetDirectly[pawn.Ideo] = new(pawn, newThing);
|
||||
});
|
||||
}
|
||||
public static bool Prefix(ThoughtWorker_Precept_AltarSharing __instance, Pawn pawn, ref Thing? __result, out bool __state)
|
||||
{
|
||||
if (!pawn.Spawned)
|
||||
{
|
||||
__result = null;
|
||||
return __state = false;
|
||||
}
|
||||
SharedAltarCacheValue cache;
|
||||
var shouldStartRefreshing = false;
|
||||
lock (Lock)
|
||||
{
|
||||
if (!SharedAltarCache.GetDirectly.TryGetValue(pawn.Ideo, out cache) || (!FishSettings.ThreadingEnabled && cache.ShouldRefreshNow))
|
||||
{
|
||||
return __state = true;
|
||||
}
|
||||
else if (cache.ShouldRefreshNow)
|
||||
{
|
||||
cache.currentlyRefreshing = true;
|
||||
SharedAltarCache.GetDirectly[pawn.Ideo] = cache;
|
||||
shouldStartRefreshing = true;
|
||||
}
|
||||
}
|
||||
|
||||
__result = cache.thing;
|
||||
if (shouldStartRefreshing)
|
||||
AsyncThoughtWorker(__instance, pawn);
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(Pawn pawn, Thing __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
lock (Lock)
|
||||
SharedAltarCache.GetDirectly[pawn.Ideo] = new(pawn, __result);
|
||||
}
|
||||
public static Thing? SharedAltarParallel(Pawn pawn, Thing[] allThings)
|
||||
{
|
||||
if (!pawn.Spawned || pawn.Ideo == null)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < allThings.Length; i++)
|
||||
{
|
||||
var item = allThings[i];
|
||||
var compStyleable = item.TryGetComp<CompStyleable>();
|
||||
if (compStyleable == null || compStyleable.SourcePrecept == null || compStyleable.SourcePrecept.ideo != pawn.Ideo)
|
||||
continue;
|
||||
|
||||
var room = item.GetRoom();
|
||||
if (room == null || room.TouchesMapEdge)
|
||||
continue;
|
||||
|
||||
var containedAndAdjacentThings = room.ContainedAndAdjacentThings;
|
||||
for (var k = 0; k < containedAndAdjacentThings.Count; k++)
|
||||
{
|
||||
var containedAndAdjacentThing = containedAndAdjacentThings[k];
|
||||
if (containedAndAdjacentThing != item)
|
||||
{
|
||||
var compStyleable2 = containedAndAdjacentThing.TryGetComp<CompStyleable>();
|
||||
if (compStyleable2 != null && compStyleable2.SourcePrecept != null && compStyleable2.SourcePrecept.ideo != pawn.Ideo)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static object Lock { get; } = new();
|
||||
}
|
||||
|
||||
public struct SharedAltarCacheValue : Cache.IIsRefreshable<Ideo, SharedAltarCacheValue>
|
||||
{
|
||||
public Thing? thing;
|
||||
public bool currentlyRefreshing;
|
||||
private int _nextLateRefreshTick;
|
||||
private int _nextEarlyRefreshTick;
|
||||
private int _allStructuresListVersion;
|
||||
private ListerThings _lister;
|
||||
|
||||
public SharedAltarCacheValue(Pawn pawn, Thing? result)
|
||||
{
|
||||
thing = result;
|
||||
var ticks = Current.Game.tickManager.TicksGame;
|
||||
_nextLateRefreshTick = ticks + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_nextEarlyRefreshTick = ticks + 128 + Math.Abs(Rand.Int % 128);
|
||||
_lister = pawn.Map.listerThings;
|
||||
_allStructuresListVersion = _lister.listsByGroup[(int)ThingRequestGroup.BuildingArtificial].Version();
|
||||
currentlyRefreshing = false;
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get
|
||||
{
|
||||
var ticks = Current.Game.tickManager.TicksGame;
|
||||
return !currentlyRefreshing && (_nextLateRefreshTick < ticks
|
||||
|| (_nextEarlyRefreshTick < ticks && _allStructuresListVersion != _lister.listsByGroup[(int)ThingRequestGroup.BuildingArtificial].Version()));
|
||||
}
|
||||
set
|
||||
{
|
||||
var ticks = Current.Game.tickManager.TicksGame;
|
||||
_nextLateRefreshTick = value ? 0 : ticks + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
_nextEarlyRefreshTick = value ? 0 : ticks + 128 + Math.Abs(Rand.Int % 128);
|
||||
}
|
||||
}
|
||||
|
||||
public SharedAltarCacheValue SetNewValue(Ideo key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Scribe_Deep normally creates a new instance when loading data. Scribe on this class prevents that. Also includes a try-catch block
|
||||
/// </summary>
|
||||
public class Exposable : IExposable
|
||||
{
|
||||
public IExposable Parent;
|
||||
public Exposable(IExposable @this) => Parent = @this;
|
||||
public void ExposeData()
|
||||
{
|
||||
try
|
||||
{
|
||||
Parent.ExposeData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
}
|
||||
}
|
||||
public override string ToString() => TrimName(Parent.ToString());
|
||||
public override bool Equals(object obj) => obj.Equals(Parent);
|
||||
public override int GetHashCode() => Parent.GetHashCode();
|
||||
|
||||
public static void Scribe(IExposable iexposable, string label, bool trimLabel = true)
|
||||
{
|
||||
var wrappedExposable = new Exposable(iexposable);
|
||||
Scribe_Deep.Look(ref wrappedExposable,
|
||||
trimLabel ? TrimName(label) : label,
|
||||
new[] { iexposable });
|
||||
}
|
||||
|
||||
internal static string TrimName(string name)
|
||||
=> name.Replace('+', '.') is var fullName
|
||||
&& $"{typeof(PerformanceFishMod).Namespace}." is var @namespace
|
||||
&& fullName.StartsWith(@namespace)
|
||||
? fullName.Remove(0, @namespace.Length)
|
||||
: fullName;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerformanceFish.Utility;
|
||||
|
||||
public class KeyedList<TKey, TValue> : List<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>
|
||||
{
|
||||
public IList<TKey> Keys { get; }
|
||||
public IList<TValue> Values { get; }
|
||||
|
||||
public KeyedList()
|
||||
{
|
||||
Keys = new KeyCollection(this);
|
||||
Values = new ValueCollection(this);
|
||||
}
|
||||
|
||||
public KeyedList(int capacity) : base(capacity)
|
||||
{
|
||||
Keys = new KeyCollection(this);
|
||||
Values = new ValueCollection(this);
|
||||
}
|
||||
|
||||
public KeyedList(IEnumerable<KeyValuePair<TKey, TValue>> collection) : base(collection)
|
||||
{
|
||||
Keys = new KeyCollection(this);
|
||||
Values = new ValueCollection(this);
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var (itemKey, itemValue) = items[i];
|
||||
if (GetKeyComparer().Equals(itemKey, key))
|
||||
return itemValue;
|
||||
}
|
||||
|
||||
return ThrowHelperF.ThrowKeyNotFoundException<TKey, TValue>(key);
|
||||
}
|
||||
set
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
ref var item = ref items[i];
|
||||
if (GetKeyComparer().Equals(item.Key, key))
|
||||
{
|
||||
item = new(item.Key, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Add(new(key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value) => Add(new(key, value));
|
||||
|
||||
public bool ContainsKey(TKey item)
|
||||
{
|
||||
Guard.IsNotNull(item);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetKeyComparer().Equals(items[i].Key, item))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ContainsValue(TValue item)
|
||||
{
|
||||
if (item is null)
|
||||
return false;
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetValueComparer().Equals(items[i].Value, item))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma warning disable CS8767 // missing nullable on IDictionary interface
|
||||
public bool TryGetValue(TKey key, out TValue? value)
|
||||
#pragma warning restore CS8767
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var index = IndexOfKey(key);
|
||||
if (index < 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = _items[index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CopyTo(TKey[] array, int arrayIndex)
|
||||
{
|
||||
CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Key;
|
||||
}
|
||||
public void CopyTo(TValue[] array, int arrayIndex)
|
||||
{
|
||||
CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Value;
|
||||
}
|
||||
|
||||
protected void CanCopyTo<W>(W[] array, int arrayIndex)
|
||||
{
|
||||
Guard.IsNotNull(array, nameof(array));
|
||||
Guard.IsGreaterThanOrEqualTo(arrayIndex, 0, nameof(arrayIndex));
|
||||
Guard.HasSizeGreaterThanOrEqualTo(array, Count + arrayIndex, nameof(array));
|
||||
}
|
||||
|
||||
public bool Remove(TKey item)
|
||||
{
|
||||
Guard.IsNotNull(item);
|
||||
|
||||
var index = IndexOfKey(item);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveValue(TValue item)
|
||||
{
|
||||
var index = IndexOfValue(item);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int IndexOfKey(TKey key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetKeyComparer().Equals(items[i].Key, key))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int IndexOfValue(TValue value)
|
||||
{
|
||||
var items = _items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (GetValueComparer().Equals(items[i].Value, value))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected class KeyCollection : IList<TKey>
|
||||
{
|
||||
private readonly KeyedList<TKey, TValue> _list;
|
||||
|
||||
public KeyCollection(KeyedList<TKey, TValue> list)
|
||||
=> _list = list;
|
||||
|
||||
public TKey this[int index]
|
||||
{
|
||||
get => _list[index].Key;
|
||||
set => ThrowHelper.ThrowNotSupportedException();
|
||||
}
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(TKey item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public void Clear() => _list.Clear();
|
||||
|
||||
public bool Contains(TKey item) => _list.ContainsKey(item);
|
||||
|
||||
public void CopyTo(TKey[] array, int arrayIndex)
|
||||
{
|
||||
_list.CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Key;
|
||||
}
|
||||
|
||||
public IEnumerator<TKey> GetEnumerator()
|
||||
{
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
yield return items[i].Key;
|
||||
}
|
||||
|
||||
public int IndexOf(TKey item) => _list.IndexOfKey(item);
|
||||
|
||||
public void Insert(int index, TKey item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public bool Remove(TKey item) => _list.Remove(item);
|
||||
|
||||
public void RemoveAt(int index) => _list.RemoveAt(index);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
protected class ValueCollection : IList<TValue>
|
||||
{
|
||||
private readonly KeyedList<TKey, TValue> _list;
|
||||
|
||||
public ValueCollection(KeyedList<TKey, TValue> list)
|
||||
=> _list = list;
|
||||
|
||||
public TValue this[int index]
|
||||
{
|
||||
get => _list[index].Value;
|
||||
set
|
||||
{
|
||||
ref var item = ref _list._items[index];
|
||||
item = new(item.Key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(TValue item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public void Clear() => _list.Clear();
|
||||
|
||||
public bool Contains(TValue item) => _list.ContainsValue(item);
|
||||
|
||||
public void CopyTo(TValue[] array, int arrayIndex)
|
||||
{
|
||||
_list.CanCopyTo(array, arrayIndex);
|
||||
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
array[i + arrayIndex] = items[i].Value;
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator()
|
||||
{
|
||||
var items = _list._items;
|
||||
for (var i = 0; i < Count; i++)
|
||||
yield return items[i].Value;
|
||||
}
|
||||
|
||||
public int IndexOf(TValue item) => _list.IndexOfValue(item);
|
||||
|
||||
public void Insert(int index, TValue item) => ThrowHelper.ThrowNotSupportedException();
|
||||
|
||||
public bool Remove(TValue item) => _list.RemoveValue(item);
|
||||
|
||||
public void RemoveAt(int index) => _list.RemoveAt(index);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static IEqualityComparer<TKey> GetKeyComparer() => EqualityComparer<TKey>.Default;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static IEqualityComparer<TValue> GetValueComparer() => EqualityComparer<TValue>.Default;
|
||||
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys => Keys;
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values => Values;
|
||||
|
||||
protected static class StringResources
|
||||
{
|
||||
internal const string ARG_KEY_NOT_FOUND_WITH_KEY = "The given key '{0}' was not present in the dictionary.";
|
||||
}
|
||||
|
||||
protected static class ThrowHelperF
|
||||
{
|
||||
[DoesNotReturn]
|
||||
internal static V ThrowKeyNotFoundException<T, V>(T key)
|
||||
=> throw GetKeyNotFoundException(key);
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException<T>(T key)
|
||||
// Generic key to move the boxing to the right hand side of throw
|
||||
=> throw GetKeyNotFoundException(key);
|
||||
|
||||
private static KeyNotFoundException GetKeyNotFoundException(object? key)
|
||||
=> new(string.Format(StringResources.ARG_KEY_NOT_FOUND_WITH_KEY, key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class UtilityPatches : ClassWithFishPatches
|
||||
{
|
||||
public class FinalizeInit_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Necessary for patches that require a loaded game to function";
|
||||
public override Expression<Action> TargetMethod => () => default(Game)!.FinalizeInit();
|
||||
public static void Postfix()
|
||||
{
|
||||
foreach (var patch in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
if (patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.PatchAll();
|
||||
}
|
||||
|
||||
Benchmarking.SwitchFrame = GenTicks.TicksGame + 6000;
|
||||
Benchmarking.EndFrame = Benchmarking.SwitchFrame + 6000;
|
||||
}
|
||||
}
|
||||
|
||||
public class ClearAllMapsAndWorld_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "A patch to make the game clear all Performance Fish caches whenever saves get unloaded, to avoid anything getting carried over to a new game by mistake";
|
||||
public override Delegate TargetMethodGroup => Verse.Profile.MemoryUtility.ClearAllMapsAndWorld;
|
||||
public static void Postfix()
|
||||
{
|
||||
Cache.Utility.Clear();
|
||||
foreach (var patch in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
if (patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.UnpatchAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user