diff --git a/doc/KKAPI.Utilities.md b/doc/KKAPI.Utilities.md index a1183c6..6f14be3 100644 --- a/doc/KKAPI.Utilities.md +++ b/doc/KKAPI.Utilities.md @@ -88,6 +88,7 @@ Static Methods | `Transform` | GetTopmostParent(this `Transform` src) | Get the topmost parent of Transform that this this Component is attached to. | | `Boolean` | IsDestroyed(this `Object` obj) | Return true if the object is a "fake" null (i.e. it was destroyed). | | `void` | MarkXuaIgnored(this `Component` target) | Mark GameObject of this Component as ignored by AutoTranslator. Prevents AutoTranslator from trying to translate custom UI elements. | +| `Boolean` | SequenceEqualFast(this `byte[]` a, `Byte[]` b) | This method compares two byte arrays for equality, returning true if they are identical and false otherwise. This method is optimized for high performance. | | `Boolean` | SetFieldValue(this `Object` self, `String` name, `Object` value) | Set value of a field through reflection | | `Boolean` | SetPropertyValue(this `Object` self, `String` name, `Object` value) | Set value of a property through reflection | | `ReadOnlyDictionary` | ToReadOnlyDictionary(this `IDictionary` original) | Wrap this dictionary in a read-only wrapper that will prevent any changes to it. Warning: Any reference types inside the dictionary can still be modified. | @@ -434,5 +435,3 @@ Static Methods | Type | Name | Summary | | --- | --- | --- | | `Int32` | LogicalCompare(`String` x, `String` y) | Compare two strings with rules used by Windows Explorer to logically sort files. | - - diff --git a/src/Shared.Core/Utilities/Extensions.cs b/src/Shared.Core/Utilities/Extensions.cs index e04119d..9de88ab 100644 --- a/src/Shared.Core/Utilities/Extensions.cs +++ b/src/Shared.Core/Utilities/Extensions.cs @@ -227,7 +227,7 @@ public static bool SetFieldValue(this object self, string name, object value) { return Traverse.Create(self).Field(name).SetValue(value).FieldExists(); } - + internal static void SafeInvoke(this T handler, Action invokeCallback) where T : Delegate { if (handler == null) return; @@ -278,5 +278,126 @@ internal static void SafeInvokeWithLogging(this T handler, Action invokeCa KoikatuAPI.Logger.LogError("Unexpected crash when running events, some events might have been skipped! Reason: " + e); } } + + /// + /// Compares two byte arrays for equality in a high-performance manner using unsafe code. + /// + /// The first byte array to compare. + /// The second byte array to compare. + /// True if the byte arrays are equal, false otherwise. + public static bool SequenceEqualFast(this byte[] a, byte[] b) + { + // Check if both references are the same, if so, return true. + if (System.Object.ReferenceEquals(a, b)) + return true; + + if (a == null || b == null) + return false; + + int bytes = a.Length; + + if (bytes != b.Length) + return false; + + if (bytes <= 0) + return true; + + unsafe + { + // Fix the memory locations of the arrays to prevent the garbage collector from moving them. + fixed (byte* pA = &a[0]) + fixed (byte* pB = &b[0]) + { + int offset = 0; + + // If both pointers are 8-byte aligned, use 64-bit comparison. + if (((int)pA & 7) == 0 && ((int)pB & 7) == 0 && bytes >= 32) + { + offset = bytes & ~31; // Round down to the nearest multiple of 32. + + byte* pA_ = pA; + byte* pB_ = pB; + byte* pALast = pA + offset; + + do + { + if (*(ulong*)pA_ != *(ulong*)pB_) + goto NotEquals; + + pA_ += 8; + pB_ += 8; + + if (*(ulong*)pA_ != *(ulong*)pB_) + goto NotEquals; + + pA_ += 8; + pB_ += 8; + + if (*(ulong*)pA_ != *(ulong*)pB_) + goto NotEquals; + + pA_ += 8; + pB_ += 8; + + if (*(ulong*)pA_ != *(ulong*)pB_) + goto NotEquals; + + pA_ += 8; + pB_ += 8; + } + while (pA_ != pALast); + } + // If both pointers are 4-byte aligned, use 32-bit comparison. + else if (((int)pA & 3) == 0 && ((int)pB & 3) == 0 && bytes >= 16) + { + offset = bytes & ~15; // Round down to the nearest multiple of 16. + + byte* pA_ = pA; + byte* pB_ = pB; + byte* pALast = pA + offset; + + do + { + if (*(uint*)pA_ != *(uint*)pB_) + goto NotEquals; + + pA_ += 4; + pB_ += 4; + + if (*(uint*)pA_ != *(uint*)pB_) + goto NotEquals; + + pA_ += 4; + pB_ += 4; + + if (*(uint*)pA_ != *(uint*)pB_) + goto NotEquals; + + pA_ += 4; + pB_ += 4; + + if (*(uint*)pA_ != *(uint*)pB_) + goto NotEquals; + + pA_ += 4; + pB_ += 4; + } + while (pA_ != pALast); + } + + // Compare remaining bytes one by one. + for (int i = offset; i < bytes; ++i) + if (pA[i] != pB[i]) + goto NotEquals; + } + } + + return true; + + NotEquals: + // Return false indicating arrays are not equal. + // Note: Using a return statement in the loop can potentially degrade performance due to the generated binary code, + return false; + } } } diff --git a/src/Shared.Core/Utilities/TextureStorage.cs b/src/Shared.Core/Utilities/TextureStorage.cs index bd48db0..c06016c 100644 --- a/src/Shared.Core/Utilities/TextureStorage.cs +++ b/src/Shared.Core/Utilities/TextureStorage.cs @@ -144,7 +144,7 @@ public int StoreTexture(byte[] tex) if (tex == null) throw new ArgumentNullException(nameof(tex)); lock (_data) { - var existing = _data.FirstOrDefault(x => x.Value != null && x.Value.Data.SequenceEqual(tex)); + var existing = _data.FirstOrDefault(x => x.Value != null && x.Value.Data.SequenceEqualFast(tex)); if (existing.Value != null) { Console.WriteLine("StoreTexture - Texture already exists, reusing it");