Less allocation, minor tweaks

NET 8 will have a Shuffle method, which can get rid of the overload in Util.
Batch Editor no longer crashes the program when selecting OT_Trash/HT_Trash/Nickname_Trash via dropdown.
This commit is contained in:
Kurt 2023-03-27 00:11:42 -07:00
parent 82309cf99d
commit 5c13183d02
17 changed files with 119 additions and 99 deletions

View file

@ -43,7 +43,7 @@ public static class BallApplicator
Span<Ball> balls = stackalloc Ball[MaxBallSpanAlloc];
var count = GetBallListFromColor(pk, balls);
balls = balls[..count];
Util.Shuffle(balls);
Util.Rand.Shuffle(balls);
return ApplyFirstLegalBall(pk, balls);
}

View file

@ -59,7 +59,7 @@ public static class MoveSetApplicator
{
la.GetSuggestedCurrentMoves(moves, random ? MoveSourceType.All : MoveSourceType.Encounter);
if (random && !la.Entity.IsEgg)
Util.Shuffle(moves);
Util.Rand.Shuffle(moves);
}
/// <summary>

View file

@ -453,17 +453,12 @@ public static class BatchEditing
{
switch (cmd.PropertyName)
{
case nameof(PKM.Nickname_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.Nickname_Trash); return ModifyResult.Modified;
case nameof(PKM.OT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.OT_Trash); return ModifyResult.Modified;
case nameof(PKM.HT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.HT_Trash); return ModifyResult.Modified;
case nameof(PKM.Nickname_Trash): StringUtil.LoadHexBytesTo(cmd.PropertyValue.AsSpan(CONST_BYTES.Length), pk.Nickname_Trash, 3); return ModifyResult.Modified;
case nameof(PKM.OT_Trash): StringUtil.LoadHexBytesTo(cmd.PropertyValue.AsSpan(CONST_BYTES.Length), pk.OT_Trash, 3); return ModifyResult.Modified;
case nameof(PKM.HT_Trash): StringUtil.LoadHexBytesTo(cmd.PropertyValue.AsSpan(CONST_BYTES.Length), pk.HT_Trash, 3); return ModifyResult.Modified;
default:
return ModifyResult.Error;
}
static byte[] ConvertToBytes(string str)
{
var arr = str[CONST_BYTES.Length..].Split(',');
return Array.ConvertAll(arr, z => Convert.ToByte(z.Trim(), 16));
}
}
/// <summary>

View file

@ -103,13 +103,11 @@ public sealed class GameDataSource
00,
};
private static IReadOnlyList<ComboItem> GetBalls(string[] itemList)
{
// ignores Poke/Great/Ultra
ReadOnlySpan<ushort> ball_nums = stackalloc ushort[] { 007, 576, 013, 492, 497, 014, 495, 493, 496, 494, 011, 498, 008, 006, 012, 015, 009, 005, 499, 010, 001, 016, 851, 1785, 1710, 1711, 1712, 1713, 1746, 1747, 1748, 1749, 1750, 1771 };
ReadOnlySpan<byte> ball_vals = stackalloc byte[] { 007, 025, 013, 017, 022, 014, 020, 018, 021, 019, 011, 023, 008, 006, 012, 015, 009, 005, 024, 010, 001, 016, 026, 0027, 0028, 0029, 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037 };
return Util.GetVariedCBListBall(itemList, ball_nums, ball_vals);
}
private static IReadOnlyList<ComboItem> GetBalls(string[] itemList) => Util.GetVariedCBListBall(itemList, BallStoredIndexes, BallItemIDs);
// Since Poké Ball (and Great Ball / Ultra Ball) are most common, any list should have them at the top. The rest can be sorted alphabetically.
private static ReadOnlySpan<byte> BallStoredIndexes => new byte[] { 004, 003, 002, 001, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 0027, 0028, 0029, 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037 };
private static ReadOnlySpan<ushort> BallItemIDs => new ushort[] { 004, 003, 002, 001, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 492, 493, 494, 495, 496, 497, 498, 499, 576, 851, 1785, 1710, 1711, 1712, 1713, 1746, 1747, 1748, 1749, 1750, 1771 };
private static ComboItem[] GetVersionList(GameStrings s)
{

View file

@ -208,7 +208,7 @@ public sealed class PK1 : GBPKML, IPersonalType
finalIVs[i] = rnd.Next(32);
for (var i = 0; i < flawless; i++)
finalIVs[i] = 31;
Util.Shuffle(finalIVs);
rnd.Shuffle(finalIVs);
pk7.SetIVs(finalIVs);
switch (IsShiny ? Shiny.Always : Shiny.Never)

View file

@ -176,7 +176,7 @@ public sealed class PK2 : GBPKML, ICaughtData2
finalIVs[i] = rnd.Next(32);
for (var i = 0; i < flawless; i++)
finalIVs[i] = 31;
Util.Shuffle(finalIVs);
rnd.Shuffle(finalIVs);
pk7.SetIVs(finalIVs);
switch (IsShiny ? Shiny.Always : Shiny.Never)

View file

@ -904,7 +904,7 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
{
for (int i = 0; i < count; i++)
ivs[i] = MaxIV;
Util.Shuffle(ivs, 0, ivs.Length, rnd); // Randomize IV order
rnd.Shuffle(ivs); // Randomize IV order
}
SetIVs(ivs);
}

View file

@ -131,20 +131,13 @@ public sealed class SK2 : GBPKM, ICaughtData2
public override bool HasOriginalMetLocation => CaughtData != 0;
public override int Version { get => (int)GameVersion.GSC; set { } }
protected override byte[] GetNonNickname(int language)
protected override void GetNonNickname(int language, Span<byte> data)
{
var name = SpeciesName.GetSpeciesNameGeneration(Species, language, 2);
byte[] data = new byte[name.Length];
StringConverter12.SetString(data, name, data.Length, Japanese, StringConverterOption.Clear50);
return data;
}
public override void SetNotNicknamed(int language)
{
var name = SpeciesName.GetSpeciesNameGeneration(Species, language, 2);
Nickname_Trash.Clear();
Nickname = name;
}
public override void SetNotNicknamed(int language) => GetNonNickname(language, Nickname_Trash);
// Maximums
public override ushort MaxMoveID => Legal.MaxMoveID_2;

View file

@ -30,13 +30,24 @@ public abstract class GBPKM : PKM
public override bool Valid { get => true; set { } }
public sealed override void RefreshChecksum() { }
protected abstract byte[] GetNonNickname(int language);
private bool? _isnicknamed;
protected abstract void GetNonNickname(int language, Span<byte> data);
public sealed override bool IsNicknamed
{
get => _isnicknamed ??= !Nickname_Trash.SequenceEqual(GetNonNickname(GuessedLanguage()));
get
{
if (_isnicknamed is {} actual)
return actual;
var current = Nickname_Trash;
Span<byte> expect = stackalloc byte[current.Length];
var language = GuessedLanguage();
GetNonNickname(language, expect);
var result = !current.SequenceEqual(expect);
_isnicknamed = result;
return result;
}
set
{
_isnicknamed = value;

View file

@ -43,24 +43,28 @@ public abstract class GBPKML : GBPKM
RawNickname.AsSpan().Fill(StringConverter12.G1TerminatorCode);
}
public override void SetNotNicknamed(int language) => GetNonNickname(language).AsSpan().CopyTo(RawNickname);
public override void SetNotNicknamed(int language) => GetNonNickname(language, RawNickname);
protected override byte[] GetNonNickname(int language)
protected override void GetNonNickname(int language, Span<byte> data)
{
var name = SpeciesName.GetSpeciesNameGeneration(Species, language, Format);
var len = Nickname_Trash.Length;
byte[] data = new byte[len];
SetString(name, data, len, StringConverterOption.Clear50);
if (!Korean)
SetString(name, data, data.Length, StringConverterOption.Clear50);
if (Korean)
return;
// Decimal point<->period fix
foreach (ref var c in data)
{
// Decimal point<->period fix
for (int i = 0; i < data.Length; i++)
{
if (data[i] == 0xF2)
data[i] = 0xE8;
}
if (c == 0xF2)
c = 0xE8;
}
return data;
}
private string GetString(ReadOnlySpan<byte> span)
{
if (Korean)
return StringConverter2KOR.GetString(span);
return StringConverter12.GetString(span, Japanese);
}
private int SetString(ReadOnlySpan<char> value, Span<byte> destBuffer, int maxLength, StringConverterOption option = StringConverterOption.None)
@ -72,12 +76,7 @@ public abstract class GBPKML : GBPKM
public sealed override string Nickname
{
get
{
if (Korean)
return StringConverter2KOR.GetString(RawNickname);
return StringConverter12.GetString(RawNickname, Japanese);
}
get => GetString(RawNickname);
set
{
if (!IsNicknamed && Nickname == value)
@ -89,12 +88,7 @@ public abstract class GBPKML : GBPKM
public sealed override string OT_Name
{
get
{
if (Korean)
return StringConverter2KOR.GetString(RawOT);
return StringConverter12.GetString(RawOT, Japanese);
}
get => GetString(RawOT);
set
{
if (value == OT_Name)

View file

@ -46,7 +46,7 @@ public static class EffortValues
break; // done!
}
Util.Shuffle(evs, 0, evs.Length, rnd);
rnd.Shuffle(evs);
}
private static void SetRandom12(Span<int> evs, Random rnd)

View file

@ -43,7 +43,7 @@ public sealed class Puff6 : SaveBlock<SAV6>
{
for (int i = 0; i < PuffSlots; i++)
Data[Offset + i] = (byte)((i % MaxPuffID) + 1);
Util.Shuffle(Data.AsSpan(), Offset, Offset + PuffSlots, rnd);
rnd.Shuffle(Data.AsSpan(Offset, PuffSlots));
}
PuffCount = PuffSlots;
}

View file

@ -112,23 +112,14 @@ public static partial class Util
cbList.Sort(beginCount, inStrings.Length, Comparer);
}
public static ComboItem[] GetVariedCBListBall(string[] inStrings, ReadOnlySpan<ushort> stringNum, ReadOnlySpan<byte> stringVal)
public static ComboItem[] GetVariedCBListBall(ReadOnlySpan<string> itemNames, ReadOnlySpan<byte> ballIndex, ReadOnlySpan<ushort> ballItemID)
{
const int forcedTop = 3; // 3 Balls are preferentially first
var list = new ComboItem[forcedTop + stringNum.Length];
list[0] = new ComboItem(inStrings[4], (int)Ball.Poke);
list[1] = new ComboItem(inStrings[3], (int)Ball.Great);
list[2] = new ComboItem(inStrings[2], (int)Ball.Ultra);
var list = new ComboItem[ballItemID.Length];
for (int i = 0; i < ballItemID.Length; i++)
list[i] = new ComboItem(itemNames[ballItemID[i]], ballIndex[i]);
for (int i = 0; i < stringNum.Length; i++)
{
int index = stringNum[i];
var value = stringVal[i];
var text = inStrings[index];
list[i + 3] = new ComboItem(text, value);
}
Array.Sort(list, 3, list.Length - 3, Comparer);
// 3 Balls are preferentially first, sort Master Ball with the rest Alphabetically.
list.AsSpan(3).Sort(Comparer);
return list;
}

View file

@ -14,23 +14,16 @@ public static partial class Util
/// Shuffles the order of items within a collection of items.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="items">Item collection</param>
public static void Shuffle<T>(Span<T> items) => Shuffle(items, 0, items.Length, Rand);
/// <summary>
/// Shuffles the order of items within a collection of items.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="items">Item collection</param>
/// <param name="start">Starting position</param>
/// <param name="end">Ending position</param>
/// <param name="rnd">RNG object to use</param>
public static void Shuffle<T>(Span<T> items, int start, int end, Random rnd)
/// <param name="items">Item collection</param>
public static void Shuffle<T>(this Random rnd, Span<T> items)
{
for (int i = start; i < end; i++)
int n = items.Length;
for (int i = 0; i < n - 1; i++)
{
int index = i + rnd.Next(end - i);
(items[index], items[i]) = (items[i], items[index]);
int j = rnd.Next(i, n);
if (j != i)
(items[i], items[j]) = (items[j], items[i]);
}
}
}

View file

@ -65,21 +65,39 @@ public static class StringUtil
}
/// <summary>
/// Converts an all-caps hex string to a byte array.
/// Converts an all-caps hex string to a byte array. Expects no separation between byte tuples.
/// </summary>
public static byte[] ToByteArray(this string toTransform)
{
var result = new byte[toTransform.Length / 2];
for (int i = 0; i < result.Length; i++)
{
var ofs = i << 1;
var _0 = toTransform[ofs + 0];
var _1 = toTransform[ofs + 1];
result[i] = DecodeTuple(_0, _1);
}
LoadHexBytesTo(toTransform, result, 2);
return result;
}
/// <summary>
/// Converts an all-caps encoded ASCII-Text hex string to a byte array.
/// </summary>
public static void LoadHexBytesTo(Span<byte> dest, ReadOnlySpan<byte> str, int tupleSize)
{
// The input string is 2-char hex values optionally separated.
// The destination array should always be larger or equal than the bytes written. Let the runtime bounds check us.
// Iterate through the string without allocating.
for (int i = 0, j = 0; i < str.Length; i += tupleSize)
dest[j++] = DecodeTuple((char)str[i + 0], (char)str[i + 1]);
}
/// <summary>
/// Converts an all-caps hex string to a byte array.
/// </summary>
public static void LoadHexBytesTo(ReadOnlySpan<char> str, Span<byte> dest, int tupleSize)
{
// The input string is 2-char hex values optionally separated.
// The destination array should always be larger or equal than the bytes written. Let the runtime bounds check us.
// Iterate through the string without allocating.
for (int i = 0, j = 0; i < str.Length; i += tupleSize)
dest[j++] = DecodeTuple(str[i + 0], str[i + 1]);
}
private static byte DecodeTuple(char _0, char _1)
{
byte result;

View file

@ -1,5 +1,6 @@
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using PKHeX.Core;
using static PKHeX.Core.MessageStrings;
@ -47,8 +48,14 @@ public partial class EntityInstructionBuilder : UserControl
L_PropType.Text = BatchEditing.GetPropertyType(CB_Property.Text, CB_Format.SelectedIndex);
if (BatchEditing.TryGetHasProperty(Entity, CB_Property.Text, out var pi))
{
L_PropValue.Text = pi.GetValue(Entity)?.ToString();
L_PropType.ForeColor = L_PropValue.ForeColor; // reset color
L_PropType.ResetForeColor();
bool hasValue = GetPropertyDisplayText(pi, Entity, out var display);
L_PropValue.Text = display;
if (hasValue)
L_PropValue.ResetForeColor();
else
L_PropValue.ForeColor = Color.Red;
}
else // no property, flag
{
@ -57,6 +64,26 @@ public partial class EntityInstructionBuilder : UserControl
}
}
private static bool GetPropertyDisplayText(PropertyInfo pi, PKM pk, out string display)
{
var type = pi.PropertyType;
if (type.IsGenericType && typeof(Span<>) == type.GetGenericTypeDefinition())
{
display = pi.PropertyType.ToString();
return false;
}
var value = pi.GetValue(pk);
if (value?.ToString() is not {} x)
{
display = "null";
return false;
}
display = x;
return true;
}
public string Create()
{
if (CB_Property.SelectedIndex < 0)

View file

@ -322,7 +322,7 @@ public partial class SAV_Inventory : Form
{
items = (ushort[])items.Clone();
if (shuffle)
Util.Shuffle(items.AsSpan());
Util.Rand.Shuffle(items.AsSpan());
Array.Resize(ref items, pouch.Items.Length);
}