Implements some micro-optimizations
All checks were successful
Build / Build (push) Successful in 51s

This commit is contained in:
Deukhoofd 2025-07-05 15:46:32 +02:00
parent c795f20e54
commit 8a857ed232
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
11 changed files with 80 additions and 21 deletions

View File

@ -59,7 +59,7 @@ public static class TestCommandRunner
Log.Debug("Starting {TaskCount} tasks", maxTasks);
await Task.WhenAll(battleTasks);
Log.Debug("Batch of {TaskCount} tasks completed", maxTasks);
battleTasks = new Task[maxTasks]; // Reset tasks for the next batch
Array.Fill(battleTasks, Task.CompletedTask); // Reset tasks for the next batch
}
}

View File

@ -173,7 +173,7 @@ public static class MoveTurnExecutor
hitData.Type = hitType;
var types = target.Types.ToList();
var types = new List<TypeIdentifier>(target.Types);
executingMove.RunScriptHook(x => x.ChangeTypesForMove(executingMove, target, hitIndex, types));
target.RunScriptHook(x => x.ChangeTypesForIncomingMove(executingMove, target, hitIndex, types));

View File

@ -1,3 +1,5 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Events;
/// <summary>
@ -10,8 +12,18 @@ namespace PkmnLib.Dynamic.Events;
/// </remarks>
public readonly record struct EventBatchId()
{
private static readonly RandomImpl RootRandom = new();
private static readonly ThreadLocal<RandomImpl> Random = new(() =>
{
lock (RootRandom)
{
return new RandomImpl(RootRandom.GetInt());
}
});
/// <summary>
/// The unique identifier for this batch of events.
/// </summary>
public Guid Id { get; init; } = Guid.NewGuid();
public Guid Id { get; init; } = Random.Value.NewGuid();
}

View File

@ -132,7 +132,7 @@ public interface IItem : INamedValue
/// </summary>
/// <param name="key">The flag to check for.</param>
/// <returns>True if the item has the flag, false otherwise.</returns>
bool HasFlag(string key);
bool HasFlag(StringKey key);
}
/// <inheritdoc />
@ -206,5 +206,5 @@ public class ItemImpl : IItem
}
/// <inheritdoc />
public bool HasFlag(string key) => Flags.Contains(key);
public bool HasFlag(StringKey key) => Flags.Contains(key);
}

View File

@ -142,7 +142,7 @@ public interface IMoveData : INamedValue
/// <summary>
/// Arbitrary flags that can be applied to the move.
/// </summary>
bool HasFlag(string key);
bool HasFlag(StringKey key);
}
/// <inheritdoc />
@ -195,5 +195,5 @@ public class MoveDataImpl : IMoveData
private readonly ImmutableHashSet<StringKey> _flags;
/// <inheritdoc />
public bool HasFlag(string key) => _flags.Contains(key);
public bool HasFlag(StringKey key) => _flags.Contains(key);
}

View File

@ -88,7 +88,7 @@ public interface IForm : INamedValue
/// <summary>
/// Check if the form has a specific flag set.
/// </summary>
bool HasFlag(string key);
bool HasFlag(StringKey key);
/// <summary>
/// Check if the form is a battle-only form, meaning it should return to its original form after the battle ends.
@ -208,7 +208,7 @@ public class FormImpl : IForm
public StringKey GetRandomHiddenAbility(IRandom rand) => HiddenAbilities[rand.GetInt(HiddenAbilities.Count)];
/// <inheritdoc />
public bool HasFlag(string key) => Flags.Contains(key);
public bool HasFlag(StringKey key) => Flags.Contains(key);
/// <inheritdoc />
public bool IsBattleOnlyForm { get; }

View File

@ -64,7 +64,7 @@ public interface ISpecies : INamedValue
/// <summary>
/// Check whether the Pokémon has a specific flag set.
/// </summary>
bool HasFlag(string key);
bool HasFlag(StringKey key);
/// <summary>
/// The data regarding into which Pokémon this species can evolve, and how.
@ -147,5 +147,5 @@ public class SpeciesImpl : ISpecies
}
/// <inheritdoc />
public bool HasFlag(string key) => Flags.Contains(key);
public bool HasFlag(StringKey key) => Flags.Contains(key);
}

View File

@ -6,7 +6,7 @@ namespace PkmnLib.Static;
/// <summary>
/// A number that identifies a type. To be used with <see cref="TypeLibrary"/>
/// </summary>
public readonly record struct TypeIdentifier
public readonly struct TypeIdentifier : IEquatable<TypeIdentifier>
{
/// <summary>
/// The name of the type identifier.
@ -27,4 +27,13 @@ public readonly record struct TypeIdentifier
/// <inheritdoc />
public override int GetHashCode() => Value.GetHashCode();
/// <inheritdoc />
public bool Equals(TypeIdentifier other) => Value == other.Value;
/// <inheritdoc />
public override bool Equals(object? obj) => obj is TypeIdentifier other && Equals(other);
public static bool operator ==(TypeIdentifier left, TypeIdentifier right) => left.Equals(right);
public static bool operator !=(TypeIdentifier left, TypeIdentifier right) => !left.Equals(right);
}

View File

@ -107,4 +107,21 @@ public class RandomImpl : IRandom
throw new ArgumentException("List cannot be empty.", nameof(list));
return list[GetInt(list.Count)];
}
/// <summary>
/// Generates a new random <see cref="Guid"/>.
/// </summary>
/// <remarks>
/// The goal of this method is to create a new unique identifier that can be used for various purposes,
/// without having to rely on the system's built-in GUID generation. The built-in GUID generation
/// can be slow (see also: https://github.com/dotnet/runtime/issues/13628)
/// </remarks>
public Guid NewGuid()
{
var guidBytes = GuidCache.Value.AsSpan();
_random.NextBytes(guidBytes);
return new Guid(guidBytes);
}
private static readonly ThreadLocal<byte[]> GuidCache = new(() => new byte[16]);
}

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace PkmnLib.Static.Utils;
@ -8,9 +9,12 @@ namespace PkmnLib.Static.Utils;
/// <remarks>
/// This is a struct, as it's effectively just a wrapper around a single reference object. Heap allocation would be silly.
/// </remarks>
public readonly record struct StringKey
public readonly struct StringKey : IEquatable<StringKey>, IEquatable<string>
{
private static readonly ConcurrentDictionary<string, int> HashCodes = new();
private readonly string _key;
private readonly int _hashCode;
/// <inheritdoc cref="StringKey"/>
public StringKey(string key)
@ -18,6 +22,10 @@ public readonly record struct StringKey
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key cannot be null or whitespace.", nameof(key));
_key = key;
if (!HashCodes.TryGetValue(_key, out _hashCode))
{
_hashCode = HashCodes[_key] = StringComparer.InvariantCultureIgnoreCase.GetHashCode(_key);
}
}
/// <summary>
@ -45,19 +53,24 @@ public readonly record struct StringKey
public override string ToString() => _key.ToLowerInvariant();
/// <inheritdoc />
public bool Equals(StringKey other) => string.Equals(_key, other._key, StringComparison.InvariantCultureIgnoreCase);
public override bool Equals(object? obj)
{
return obj switch
{
StringKey other => Equals(other),
string str => Equals(str),
_ => false,
};
}
/// <inheritdoc />
public bool Equals(StringKey other) => _hashCode == other._hashCode;
/// <inheritdoc cref="Equals(StringKey)"/>
public bool Equals(string other) => string.Equals(_key, other, StringComparison.InvariantCultureIgnoreCase);
/// <inheritdoc />
public override int GetHashCode() => StringComparer.InvariantCultureIgnoreCase.GetHashCode(_key);
/// <inheritdoc cref="Equals(StringKey)"/>
public static bool operator ==(StringKey left, string right) => left.Equals(right);
/// <inheritdoc cref="Equals(StringKey)"/>
public static bool operator !=(StringKey left, string right) => !(left == right);
public override int GetHashCode() => _hashCode;
/// <inheritdoc cref="Equals(StringKey)"/>
public static bool operator ==(StringKey? left, string? right) =>

View File

@ -22,6 +22,14 @@ public class StringKeyTests
await Assert.That(sk1 == sk2).IsEqualTo(expected);
}
[Test, MethodDataSource(nameof(StringKeyEqualityTestCases))]
public async Task StringKeyInequalityTest(string k1, string k2, bool expected)
{
var sk1 = new StringKey(k1);
var sk2 = new StringKey(k2);
await Assert.That(sk1 != sk2).IsNotEqualTo(expected);
}
[Test, MethodDataSource(nameof(StringKeyEqualityTestCases))]
public async Task HashCodeEqualityTest(string k1, string k2, bool expected)
{