mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Add optional delegate for mid-batch-modify
Currently unused by everything; allows a compiled function to be run between the Filters and Modifiers
This commit is contained in:
parent
13fc0cdfeb
commit
b6ae27e4e8
|
|
@ -176,7 +176,7 @@ public static bool TryGetPropertyType(string propertyName, [NotNullWhen(true)] o
|
|||
{
|
||||
if (CustomProperties.Contains(propertyName))
|
||||
{
|
||||
result ="Custom";
|
||||
result = "Custom";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -344,27 +344,28 @@ public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object
|
|||
/// <param name="pk">Object to modify.</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <param name="modifier">Custom modifier delegate.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
public static bool TryModify(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
|
||||
{
|
||||
var result = TryModifyPKM(pk, filters, modifications);
|
||||
return result == ModifyResult.Modified;
|
||||
}
|
||||
public static bool TryModify(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<PKM, bool>? modifier = null)
|
||||
=> TryModifyPKM(pk, filters, modifications, modifier) is ModifyResult.Modified;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the <see cref="BatchInfo"/>.
|
||||
/// Tries to modify the <see cref="PKM"/> using instructions and a custom modifier delegate.
|
||||
/// </summary>
|
||||
/// <param name="pk">Command Filter</param>
|
||||
/// <param name="pk">Object to modify.</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <param name="modifier">Custom modifier delegate.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
|
||||
internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
if (!pk.ChecksumValid || pk.Species == 0)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
var info = new BatchInfo(pk);
|
||||
var props = GetProps(pk);
|
||||
|
||||
// Check if any filter requires us to exclude this from modification scope.
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
try
|
||||
|
|
@ -372,7 +373,6 @@ internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction>
|
|||
if (!IsFilterMatch(cmd, info, props))
|
||||
return ModifyResult.Filtered;
|
||||
}
|
||||
// Swallow any error because this can be malformed user input.
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(MsgBEModifyFailCompare + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
|
||||
|
|
@ -380,8 +380,26 @@ internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction>
|
|||
}
|
||||
}
|
||||
|
||||
// Run all modifications, and track if any modifications were made or if any errors occurred.
|
||||
var error = false;
|
||||
var result = ModifyResult.Skipped;
|
||||
|
||||
// If a compiled modifier is provided, execute it. If it returns false, skip further modifications.
|
||||
if (modifier is { } func)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!func(pk))
|
||||
return ModifyResult.Skipped;
|
||||
result = ModifyResult.Modified;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var cmd in modifications)
|
||||
{
|
||||
try
|
||||
|
|
@ -392,7 +410,6 @@ internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction>
|
|||
else if (tmp != ModifyResult.Skipped)
|
||||
result = tmp;
|
||||
}
|
||||
// Swallow any error because this can be malformed user input.
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(MsgBEModifyFail + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
|
||||
|
|
@ -437,24 +454,19 @@ private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info
|
|||
if (cmd.Operation != InstructionOperation.Set)
|
||||
return ApplyNumericOperation(pk, cmd, pi, props);
|
||||
|
||||
object val;
|
||||
if (cmd.Random)
|
||||
val = cmd.RandomValue;
|
||||
else if (cmd.PropertyValue.StartsWith(CONST_POINTER) && props.TryGetValue(cmd.PropertyValue.AsSpan(1), out var opi))
|
||||
val = opi.GetValue(pk) ?? throw new NullReferenceException();
|
||||
else
|
||||
val = cmd.PropertyValue;
|
||||
if (!TryResolveOperandValue(cmd, pk, props, out var value))
|
||||
return ModifyResult.Error;
|
||||
|
||||
ReflectUtil.SetValue(pi, pk, val);
|
||||
ReflectUtil.SetValue(pi, pk, value);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
private static ModifyResult ApplyNumericOperation(PKM pk, StringInstruction cmd, PropertyInfo pi, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
|
||||
private static ModifyResult ApplyNumericOperation<T>(T pk, StringInstruction cmd, PropertyInfo pi, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props) where T : notnull
|
||||
{
|
||||
if (!pi.CanRead)
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!TryGetNumericType(pi.PropertyType, out var numericType, out var isNullable))
|
||||
if (!TryGetNumericType(pi.PropertyType, out var numericType))
|
||||
return ModifyResult.Error;
|
||||
|
||||
var currentValue = pi.GetValue(pk);
|
||||
|
|
@ -464,15 +476,14 @@ private static ModifyResult ApplyNumericOperation(PKM pk, StringInstruction cmd,
|
|||
if (!TryResolveOperandValue(cmd, pk, props, out var operandValue))
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!TryApplyNumericOperation(numericType, currentValue, operandValue, cmd.Operation, out var result))
|
||||
if (!TryApplyNumericOperation(numericType, currentValue, operandValue, cmd.Operation, out var value))
|
||||
return ModifyResult.Error;
|
||||
|
||||
var valueToSet = isNullable ? Activator.CreateInstance(pi.PropertyType, result) ?? result : result;
|
||||
pi.SetValue(pk, valueToSet);
|
||||
ReflectUtil.SetValue(pi, pk, value);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
private static bool TryResolveOperandValue(StringInstruction cmd, PKM pk, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props, [NotNullWhen(true)] out object? value)
|
||||
private static bool TryResolveOperandValue<T>(StringInstruction cmd, T pk, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props, [NotNullWhen(true)] out object? value)
|
||||
{
|
||||
if (cmd.Random)
|
||||
{
|
||||
|
|
@ -483,44 +494,76 @@ private static bool TryResolveOperandValue(StringInstruction cmd, PKM pk, Dictio
|
|||
var propertyValue = cmd.PropertyValue;
|
||||
if (propertyValue.StartsWith(CONST_POINTER) && props.TryGetValue(propertyValue.AsSpan(1), out var opi))
|
||||
{
|
||||
value = opi.GetValue(pk) ?? throw new NullReferenceException();
|
||||
return true;
|
||||
value = opi.GetValue(pk);
|
||||
return value is not null;
|
||||
}
|
||||
|
||||
value = propertyValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetNumericType(Type type, out Type numericType, out bool isNullable)
|
||||
private static bool TryGetNumericType(Type type, out Type numericType)
|
||||
{
|
||||
numericType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
isNullable = numericType != type;
|
||||
// isNullable = numericType != type;
|
||||
return numericType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(INumber<>));
|
||||
}
|
||||
|
||||
private static bool TryApplyNumericOperation(Type numericType, object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
|
||||
{
|
||||
var methodName = operation is InstructionOperation.BitwiseAnd or InstructionOperation.BitwiseOr or InstructionOperation.BitwiseXor
|
||||
or InstructionOperation.BitwiseShiftLeft or InstructionOperation.BitwiseShiftRight
|
||||
? nameof(TryApplyBinaryIntegerOperationCore)
|
||||
: nameof(TryApplyNumericOperationCore);
|
||||
result = null;
|
||||
if (numericType == typeof(byte))
|
||||
return ApplyBinaryInteger<byte>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(sbyte))
|
||||
return ApplyBinaryInteger<sbyte>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(short))
|
||||
return ApplyBinaryInteger<short>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(ushort))
|
||||
return ApplyBinaryInteger<ushort>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(int))
|
||||
return ApplyBinaryInteger<int>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(uint))
|
||||
return ApplyBinaryInteger<uint>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(long))
|
||||
return ApplyBinaryInteger<long>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(ulong))
|
||||
return ApplyBinaryInteger<ulong>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(nint))
|
||||
return ApplyBinaryInteger<nint>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(nuint))
|
||||
return ApplyBinaryInteger<nuint>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(BigInteger))
|
||||
return ApplyBinaryInteger<BigInteger>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(float))
|
||||
return ApplyNumeric<float>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(double))
|
||||
return ApplyNumeric<double>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(decimal))
|
||||
return ApplyNumeric<decimal>(currentValue, operandValue, operation, out result);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (methodName == nameof(TryApplyBinaryIntegerOperationCore) && !IsBinaryIntegerType(numericType))
|
||||
private static bool ApplyNumeric<T>(object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
|
||||
where T : INumber<T>
|
||||
{
|
||||
if (IsBitwiseOperation(operation))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var method = typeof(BatchEditing).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (method is null)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
var generic = method.MakeGenericMethod(numericType);
|
||||
var parameters = new[] { currentValue, operandValue, operation, null };
|
||||
var success = (bool)(generic.Invoke(null, parameters) ?? false);
|
||||
result = parameters[3]!;
|
||||
var success = TryApplyNumericOperationCore<T>(currentValue, operandValue, operation, out var typed);
|
||||
result = typed;
|
||||
return success;
|
||||
}
|
||||
|
||||
private static bool ApplyBinaryInteger<T>(object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
|
||||
where T : IBinaryInteger<T>
|
||||
{
|
||||
var success = IsBitwiseOperation(operation)
|
||||
? TryApplyBinaryIntegerOperationCore<T>(currentValue, operandValue, operation, out var typed)
|
||||
: TryApplyNumericOperationCore(currentValue, operandValue, operation, out typed);
|
||||
result = typed;
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
@ -592,8 +635,9 @@ private static bool TryApplyBinaryIntegerOperationCore<T>(object currentValue, o
|
|||
}
|
||||
}
|
||||
|
||||
private static bool IsBinaryIntegerType(Type numericType)
|
||||
=> numericType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IBinaryInteger<>));
|
||||
private static bool IsBitwiseOperation(InstructionOperation operation)
|
||||
=> operation is InstructionOperation.BitwiseAnd or InstructionOperation.BitwiseOr or InstructionOperation.BitwiseXor
|
||||
or InstructionOperation.BitwiseShiftLeft or InstructionOperation.BitwiseShiftRight;
|
||||
|
||||
private static bool TryConvertNumeric<T>(object value, [NotNullWhen(true)] out T? result) where T : INumber<T>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@ public sealed class BatchEditor
|
|||
private int Failed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the <see cref="PKM"/>.
|
||||
/// Tries to modify the <see cref="PKM"/> using instructions and a custom modifier delegate.
|
||||
/// </summary>
|
||||
/// <param name="pk">Object to modify.</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <param name="modifier">Custom modifier delegate.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
|
||||
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
if (pk.Species == 0)
|
||||
return false;
|
||||
|
|
@ -33,13 +34,12 @@ public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<
|
|||
return false;
|
||||
}
|
||||
|
||||
var result = BatchEditing.TryModifyPKM(pk, filters, modifications);
|
||||
var result = BatchEditing.TryModifyPKM(pk, filters, modifications, modifier);
|
||||
if (result != ModifyResult.Skipped)
|
||||
Iterated++;
|
||||
if (result.HasFlag(ModifyResult.Error))
|
||||
{
|
||||
Failed++;
|
||||
// Still need to fix checksum if another modification was successful.
|
||||
result &= ~ModifyResult.Error;
|
||||
}
|
||||
if (result != ModifyResult.Modified)
|
||||
|
|
@ -73,15 +73,16 @@ public string GetEditorResults(IReadOnlyCollection<StringInstructionSet> sets)
|
|||
/// </summary>
|
||||
/// <param name="lines">Batch instruction line(s)</param>
|
||||
/// <param name="data">Entities to modify</param>
|
||||
/// <param name="modifier">Custom modifier delegate.</param>
|
||||
/// <returns>Editor object if follow-up modifications are desired.</returns>
|
||||
public static BatchEditor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data)
|
||||
public static BatchEditor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
var editor = new BatchEditor();
|
||||
var sets = StringInstructionSet.GetBatchSets(lines);
|
||||
foreach (var pk in data)
|
||||
{
|
||||
foreach (var set in sets)
|
||||
editor.Process(pk, set.Filters, set.Instructions);
|
||||
editor.Process(pk, set.Filters, set.Instructions, modifier);
|
||||
}
|
||||
|
||||
return editor;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
namespace PKHeX.Core.Tests;
|
||||
|
|
@ -51,7 +50,7 @@ public void ApplyNumericOperation(string instruction, InstructionOperation opera
|
|||
{
|
||||
StringInstruction.TryParseInstruction(instruction, out var entry).Should().BeTrue();
|
||||
entry.Should().NotBeNull();
|
||||
entry!.Operation.Should().Be(operation);
|
||||
entry.Operation.Should().Be(operation);
|
||||
|
||||
var pk = CreateTestPK7(initialValue);
|
||||
var modified = BatchEditing.TryModify(pk, [], [entry]);
|
||||
|
|
@ -70,4 +69,75 @@ private static PK7 CreateTestPK7(uint exp)
|
|||
pk.RefreshChecksum();
|
||||
return pk;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessDelegateReturnsTrueWhenModified()
|
||||
{
|
||||
var pk = CreateTestPK7(100);
|
||||
var editor = new BatchEditor();
|
||||
|
||||
bool modified = editor.Process(pk, [], [], static p =>
|
||||
{
|
||||
p.EXP = 200;
|
||||
return true;
|
||||
});
|
||||
|
||||
modified.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessDelegateUpdatesExpWhenModified()
|
||||
{
|
||||
var pk = CreateTestPK7(100);
|
||||
var editor = new BatchEditor();
|
||||
|
||||
_ = editor.Process(pk, [], [], static p =>
|
||||
{
|
||||
p.EXP = 200;
|
||||
return true;
|
||||
});
|
||||
|
||||
pk.EXP.Should().Be(200u);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessInstructionsAndDelegateUpdatesExp()
|
||||
{
|
||||
var pk = CreateTestPK7(100);
|
||||
var editor = new BatchEditor();
|
||||
|
||||
_ = editor.Process(pk, [], [], static p =>
|
||||
{
|
||||
p.EXP = 200;
|
||||
return true;
|
||||
});
|
||||
|
||||
pk.EXP.Should().Be(200u);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessInstructionsAndDelegateSkipsWhenDelegateReturnsFalse()
|
||||
{
|
||||
var pk = CreateTestPK7(100);
|
||||
var editor = new BatchEditor();
|
||||
StringInstruction.TryParseInstruction(".EXP=200", out var instruction).Should().BeTrue();
|
||||
instruction.Should().NotBeNull();
|
||||
|
||||
bool modified = editor.Process(pk, [], [instruction], static _ => false);
|
||||
|
||||
modified.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessInstructionsAndDelegatePreservesExpWhenDelegateReturnsFalse()
|
||||
{
|
||||
var pk = CreateTestPK7(100);
|
||||
var editor = new BatchEditor();
|
||||
StringInstruction.TryParseInstruction(".EXP=200", out var instruction).Should().BeTrue();
|
||||
instruction.Should().NotBeNull();
|
||||
|
||||
_ = editor.Process(pk, [], [instruction], static _ => false);
|
||||
|
||||
pk.EXP.Should().Be(100u);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ public void SimulatorParseEVsOutOfOrder(string text, int hp, int atk, int def, i
|
|||
success.Should().BeTrue("Parsing should succeed");
|
||||
set.Should().NotBeNull();
|
||||
|
||||
var evs = set!.EVs;
|
||||
var evs = set.EVs;
|
||||
evs[0].Should().Be(hp, "HP EV should match");
|
||||
evs[1].Should().Be(atk, "Atk EV should match");
|
||||
evs[2].Should().Be(def, "Def EV should match");
|
||||
|
|
@ -488,7 +488,7 @@ public void SimulatorParseIVsOutOfOrder(string text, int hp, int atk, int def, i
|
|||
success.Should().BeTrue("Parsing should succeed");
|
||||
set.Should().NotBeNull();
|
||||
|
||||
var ivs = set!.IVs;
|
||||
var ivs = set.IVs;
|
||||
ivs[0].Should().Be(hp, "HP IV should match");
|
||||
ivs[1].Should().Be(atk, "Atk IV should match");
|
||||
ivs[2].Should().Be(def, "Def IV should match");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user