From 8a857ed23281a8216a662c6c4c8fa6e7124885a0 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 5 Jul 2025 15:46:32 +0200 Subject: [PATCH] Implements some micro-optimizations --- AI/AIRunner/TestCommandRunner.cs | 2 +- .../BattleFlow/MoveTurnExecutor.cs | 2 +- .../Events/Handling/EventBatchId.cs | 14 ++++++++- PkmnLib.Static/Item.cs | 4 +-- PkmnLib.Static/Moves/MoveData.cs | 4 +-- PkmnLib.Static/Species/Form.cs | 4 +-- PkmnLib.Static/Species/Species.cs | 4 +-- PkmnLib.Static/TypeIdentifier.cs | 11 ++++++- PkmnLib.Static/Utils/Random.cs | 17 ++++++++++ PkmnLib.Static/Utils/StringKey.cs | 31 +++++++++++++------ PkmnLib.Tests/Static/StringKeyTests.cs | 8 +++++ 11 files changed, 80 insertions(+), 21 deletions(-) diff --git a/AI/AIRunner/TestCommandRunner.cs b/AI/AIRunner/TestCommandRunner.cs index df155f6..66db1a9 100644 --- a/AI/AIRunner/TestCommandRunner.cs +++ b/AI/AIRunner/TestCommandRunner.cs @@ -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 } } diff --git a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs index 5538989..29826f2 100644 --- a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs @@ -173,7 +173,7 @@ public static class MoveTurnExecutor hitData.Type = hitType; - var types = target.Types.ToList(); + var types = new List(target.Types); executingMove.RunScriptHook(x => x.ChangeTypesForMove(executingMove, target, hitIndex, types)); target.RunScriptHook(x => x.ChangeTypesForIncomingMove(executingMove, target, hitIndex, types)); diff --git a/PkmnLib.Dynamic/Events/Handling/EventBatchId.cs b/PkmnLib.Dynamic/Events/Handling/EventBatchId.cs index eee5a83..7290cdc 100644 --- a/PkmnLib.Dynamic/Events/Handling/EventBatchId.cs +++ b/PkmnLib.Dynamic/Events/Handling/EventBatchId.cs @@ -1,3 +1,5 @@ +using PkmnLib.Static.Utils; + namespace PkmnLib.Dynamic.Events; /// @@ -10,8 +12,18 @@ namespace PkmnLib.Dynamic.Events; /// public readonly record struct EventBatchId() { + private static readonly RandomImpl RootRandom = new(); + + private static readonly ThreadLocal Random = new(() => + { + lock (RootRandom) + { + return new RandomImpl(RootRandom.GetInt()); + } + }); + /// /// The unique identifier for this batch of events. /// - public Guid Id { get; init; } = Guid.NewGuid(); + public Guid Id { get; init; } = Random.Value.NewGuid(); } \ No newline at end of file diff --git a/PkmnLib.Static/Item.cs b/PkmnLib.Static/Item.cs index 57b5f3e..7cda3e0 100644 --- a/PkmnLib.Static/Item.cs +++ b/PkmnLib.Static/Item.cs @@ -132,7 +132,7 @@ public interface IItem : INamedValue /// /// The flag to check for. /// True if the item has the flag, false otherwise. - bool HasFlag(string key); + bool HasFlag(StringKey key); } /// @@ -206,5 +206,5 @@ public class ItemImpl : IItem } /// - public bool HasFlag(string key) => Flags.Contains(key); + public bool HasFlag(StringKey key) => Flags.Contains(key); } \ No newline at end of file diff --git a/PkmnLib.Static/Moves/MoveData.cs b/PkmnLib.Static/Moves/MoveData.cs index 76f480e..a04311e 100644 --- a/PkmnLib.Static/Moves/MoveData.cs +++ b/PkmnLib.Static/Moves/MoveData.cs @@ -142,7 +142,7 @@ public interface IMoveData : INamedValue /// /// Arbitrary flags that can be applied to the move. /// - bool HasFlag(string key); + bool HasFlag(StringKey key); } /// @@ -195,5 +195,5 @@ public class MoveDataImpl : IMoveData private readonly ImmutableHashSet _flags; /// - public bool HasFlag(string key) => _flags.Contains(key); + public bool HasFlag(StringKey key) => _flags.Contains(key); } \ No newline at end of file diff --git a/PkmnLib.Static/Species/Form.cs b/PkmnLib.Static/Species/Form.cs index a23a7fc..909ecba 100644 --- a/PkmnLib.Static/Species/Form.cs +++ b/PkmnLib.Static/Species/Form.cs @@ -88,7 +88,7 @@ public interface IForm : INamedValue /// /// Check if the form has a specific flag set. /// - bool HasFlag(string key); + bool HasFlag(StringKey key); /// /// 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)]; /// - public bool HasFlag(string key) => Flags.Contains(key); + public bool HasFlag(StringKey key) => Flags.Contains(key); /// public bool IsBattleOnlyForm { get; } diff --git a/PkmnLib.Static/Species/Species.cs b/PkmnLib.Static/Species/Species.cs index 3ccfe54..c51e011 100644 --- a/PkmnLib.Static/Species/Species.cs +++ b/PkmnLib.Static/Species/Species.cs @@ -64,7 +64,7 @@ public interface ISpecies : INamedValue /// /// Check whether the Pokémon has a specific flag set. /// - bool HasFlag(string key); + bool HasFlag(StringKey key); /// /// The data regarding into which Pokémon this species can evolve, and how. @@ -147,5 +147,5 @@ public class SpeciesImpl : ISpecies } /// - public bool HasFlag(string key) => Flags.Contains(key); + public bool HasFlag(StringKey key) => Flags.Contains(key); } \ No newline at end of file diff --git a/PkmnLib.Static/TypeIdentifier.cs b/PkmnLib.Static/TypeIdentifier.cs index 6984cab..d6b0843 100644 --- a/PkmnLib.Static/TypeIdentifier.cs +++ b/PkmnLib.Static/TypeIdentifier.cs @@ -6,7 +6,7 @@ namespace PkmnLib.Static; /// /// A number that identifies a type. To be used with /// -public readonly record struct TypeIdentifier +public readonly struct TypeIdentifier : IEquatable { /// /// The name of the type identifier. @@ -27,4 +27,13 @@ public readonly record struct TypeIdentifier /// public override int GetHashCode() => Value.GetHashCode(); + + /// + public bool Equals(TypeIdentifier other) => Value == other.Value; + + /// + 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); } \ No newline at end of file diff --git a/PkmnLib.Static/Utils/Random.cs b/PkmnLib.Static/Utils/Random.cs index bb5e1a5..fb36cd4 100644 --- a/PkmnLib.Static/Utils/Random.cs +++ b/PkmnLib.Static/Utils/Random.cs @@ -107,4 +107,21 @@ public class RandomImpl : IRandom throw new ArgumentException("List cannot be empty.", nameof(list)); return list[GetInt(list.Count)]; } + + /// + /// Generates a new random . + /// + /// + /// 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) + /// + public Guid NewGuid() + { + var guidBytes = GuidCache.Value.AsSpan(); + _random.NextBytes(guidBytes); + return new Guid(guidBytes); + } + + private static readonly ThreadLocal GuidCache = new(() => new byte[16]); } \ No newline at end of file diff --git a/PkmnLib.Static/Utils/StringKey.cs b/PkmnLib.Static/Utils/StringKey.cs index 334fee8..997b605 100644 --- a/PkmnLib.Static/Utils/StringKey.cs +++ b/PkmnLib.Static/Utils/StringKey.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; namespace PkmnLib.Static.Utils; @@ -8,9 +9,12 @@ namespace PkmnLib.Static.Utils; /// /// This is a struct, as it's effectively just a wrapper around a single reference object. Heap allocation would be silly. /// -public readonly record struct StringKey +public readonly struct StringKey : IEquatable, IEquatable { + private static readonly ConcurrentDictionary HashCodes = new(); + private readonly string _key; + private readonly int _hashCode; /// 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); + } } /// @@ -45,19 +53,24 @@ public readonly record struct StringKey public override string ToString() => _key.ToLowerInvariant(); /// - 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, + }; + } + + /// + public bool Equals(StringKey other) => _hashCode == other._hashCode; /// public bool Equals(string other) => string.Equals(_key, other, StringComparison.InvariantCultureIgnoreCase); /// - public override int GetHashCode() => StringComparer.InvariantCultureIgnoreCase.GetHashCode(_key); - - /// - public static bool operator ==(StringKey left, string right) => left.Equals(right); - - /// - public static bool operator !=(StringKey left, string right) => !(left == right); + public override int GetHashCode() => _hashCode; /// public static bool operator ==(StringKey? left, string? right) => diff --git a/PkmnLib.Tests/Static/StringKeyTests.cs b/PkmnLib.Tests/Static/StringKeyTests.cs index a496ca1..593ba6f 100644 --- a/PkmnLib.Tests/Static/StringKeyTests.cs +++ b/PkmnLib.Tests/Static/StringKeyTests.cs @@ -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) {