A game about forced loneliness, made by TACStudios
at master 350 lines 15 kB view raw
1using System; 2using System.Diagnostics; 3using System.Runtime.InteropServices; 4using Unity.Burst; 5using Unity.Burst.Intrinsics; 6using Unity.Collections.LowLevel.Unsafe; 7using Unity.Mathematics; 8 9namespace Unity.Collections 10{ 11 [GenerateTestsForBurstCompatibility] 12 public static partial class xxHash3 13 { 14 /// <summary> 15 /// Type used to compute hash based on multiple data feed 16 /// </summary> 17 /// <remarks> 18 /// Allow to feed the internal hashing accumulators with data through multiple calls to <see cref="Update"/>, then retrieving the final hash value using <see cref="DigestHash64"/> or <see cref="DigestHash128"/>. 19 /// More info about how to use this class in its constructor. 20 /// </remarks> 21 [GenerateTestsForBurstCompatibility] 22 public struct StreamingState 23 { 24 #region Public API 25 26 /// <summary> 27 /// Create a StreamingState object, ready to be used with the streaming API 28 /// </summary> 29 /// <param name="isHash64">true if we are computing a 64bits hash value, false if we are computing a 128bits one</param> 30 /// <param name="seed">A seed value to be used to compute the hash, default is 0</param> 31 /// <remarks> 32 /// Once the object is constructed, you can call the <see cref="Update"/> method as many times as you want to accumulate data to hash. 33 /// When all the data has been sent, call <see cref="DigestHash64"/> or <see cref="DigestHash128"/> to retrieve the corresponding key, the <see cref="StreamingState"/> 34 /// instance will then be reset, using the same hash key size and same Seed in order to be ready to be used again. 35 /// </remarks> 36 public StreamingState(bool isHash64, ulong seed=0) 37 { 38 State = default; 39 Reset(isHash64, seed); 40 } 41 42 /// <summary> 43 /// Reset the state of the streaming instance using the given seed value. 44 /// </summary> 45 /// <param name="isHash64"></param> 46 /// <param name="seed">The seed value to alter the computed hash value from</param> 47 /// <remarks> Call this method to start a new streaming session based on this instance</remarks> 48 public unsafe void Reset(bool isHash64, ulong seed=0UL) 49 { 50 // Reset the whole buffer to 0 51 var size = UnsafeUtility.SizeOf<StreamingStateData>(); 52 UnsafeUtility.MemClear(UnsafeUtility.AddressOf(ref State), size); 53 54 // Set back the saved states 55 State.IsHash64 = isHash64 ? 1 : 0; 56 57 // Init the accumulator with the prime numbers 58 var acc = Acc; 59 acc[0] = PRIME32_3; 60 acc[1] = PRIME64_1; 61 acc[2] = PRIME64_2; 62 acc[3] = PRIME64_3; 63 acc[4] = PRIME64_4; 64 acc[5] = PRIME32_2; 65 acc[6] = PRIME64_5; 66 acc[7] = PRIME32_1; 67 68 State.Seed = seed; 69 70 fixed (byte* secret = xxHashDefaultKey.kSecret) 71 { 72 if (seed != 0) 73 { 74 // Must encode the secret key if we're using a seed, we store it in the state object 75 EncodeSecretKey(SecretKey, secret, seed); 76 } 77 else 78 { 79 // Otherwise just copy it 80 UnsafeUtility.MemCpy(SecretKey, secret, SECRET_KEY_SIZE); 81 } 82 } 83 } 84 85 /// <summary> 86 /// Add some data to be hashed 87 /// </summary> 88 /// <param name="input">The memory buffer, can't be null</param> 89 /// <param name="length">The length of the data to accumulate, can be zero</param> 90 /// <remarks>This API allows you to feed very small data to be hashed, avoiding you to accumulate them in a big buffer, then computing the hash value from.</remarks> 91 public unsafe void Update(void* input, int length) 92 { 93 var bInput = (byte*) input; 94 var bEnd = bInput + length; 95 var isHash64 = State.IsHash64; 96 var secret = SecretKey; 97 State.TotalLength += length; 98 99 if (State.BufferedSize + length <= INTERNAL_BUFFER_SIZE) 100 { 101 UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, length); 102 State.BufferedSize += length; 103 return; 104 } 105 106 if (State.BufferedSize != 0) 107 { 108 var loadSize = INTERNAL_BUFFER_SIZE - State.BufferedSize; 109 UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, loadSize); 110 bInput += loadSize; 111 112 ConsumeStripes(Acc, ref State.NbStripesSoFar, Buffer, INTERNAL_BUFFER_STRIPES, secret, isHash64); 113 114 State.BufferedSize = 0; 115 } 116 117 if (bInput + INTERNAL_BUFFER_SIZE < bEnd) 118 { 119 var limit = bEnd - INTERNAL_BUFFER_SIZE; 120 do 121 { 122 ConsumeStripes(Acc, ref State.NbStripesSoFar, bInput, INTERNAL_BUFFER_STRIPES, secret, isHash64); 123 bInput += INTERNAL_BUFFER_SIZE; 124 } while (bInput < limit); 125 UnsafeUtility.MemCpy(Buffer + INTERNAL_BUFFER_SIZE - STRIPE_LEN, bInput - STRIPE_LEN, STRIPE_LEN); 126 } 127 128 if (bInput < bEnd) 129 { 130 var newBufferedSize = bEnd - bInput; 131 UnsafeUtility.MemCpy(Buffer, bInput, newBufferedSize); 132 State.BufferedSize = (int) newBufferedSize; 133 } 134 } 135 136 /// <summary> 137 /// Add the contents of input struct to the hash. 138 /// </summary> 139 /// <typeparam name="T">The input type.</typeparam> 140 /// <param name="input">The input struct that will be hashed</param> 141 /// <remarks>This API allows you to feed very small data to be hashed, avoiding you to accumulate them in a big buffer, then computing the hash value from.</remarks> 142 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] 143 public unsafe void Update<T>(in T input) where T : unmanaged 144 { 145 Update(UnsafeUtilityExtensions.AddressOf(input), UnsafeUtility.SizeOf<T>()); 146 } 147 148 149 /// <summary> 150 /// Compute the 128bits value based on all the data that have been accumulated 151 /// </summary> 152 /// <returns>The hash value</returns> 153 public unsafe uint4 DigestHash128() 154 { 155 CheckKeySize(0); 156 157 unchecked 158 { 159 var secret = SecretKey; 160 uint4 hash; 161 if (State.TotalLength > MIDSIZE_MAX) 162 { 163 var acc = stackalloc ulong[ACC_NB]; 164 DigestLong(acc, secret, 0); 165 166 var low64 = MergeAcc(acc, secret + SECRET_MERGEACCS_START, 167 (ulong) State.TotalLength * PRIME64_1); 168 var high64 = MergeAcc(acc, secret + SECRET_LIMIT - SECRET_MERGEACCS_START, 169 ~((ulong) State.TotalLength * PRIME64_2)); 170 hash = ToUint4(low64, high64); 171 } 172 else 173 { 174 hash = Hash128(Buffer, State.TotalLength, State.Seed); 175 } 176 Reset(State.IsHash64==1, State.Seed); 177 return hash; 178 } 179 } 180 181 /// <summary> 182 /// Compute the 64bits value based on all the data that have been accumulated 183 /// </summary> 184 /// <returns>The hash value</returns> 185 public unsafe uint2 DigestHash64() 186 { 187 CheckKeySize(1); 188 189 unchecked 190 { 191 var secret = SecretKey; 192 uint2 hash; 193 if (State.TotalLength > MIDSIZE_MAX) 194 { 195 var acc = stackalloc ulong[ACC_NB]; 196 DigestLong(acc, secret, 1); 197 198 hash = ToUint2(MergeAcc(acc, secret + SECRET_MERGEACCS_START, (ulong) State.TotalLength * PRIME64_1)); 199 } 200 else 201 { 202 hash = Hash64(Buffer, State.TotalLength, State.Seed); 203 } 204 Reset(State.IsHash64==1, State.Seed); 205 return hash; 206 } 207 } 208 209 #endregion 210 211 #region Constants 212 213 private static readonly int SECRET_LIMIT = SECRET_KEY_SIZE - STRIPE_LEN; 214 private static readonly int NB_STRIPES_PER_BLOCK = SECRET_LIMIT / SECRET_CONSUME_RATE; 215 private static readonly int INTERNAL_BUFFER_SIZE = 256; 216 private static readonly int INTERNAL_BUFFER_STRIPES = INTERNAL_BUFFER_SIZE / STRIPE_LEN; 217 218 #endregion 219 220 #region Wrapper to internal data storage 221 222 unsafe ulong* Acc 223 { 224 [DebuggerStepThrough] 225 get => (ulong*) UnsafeUtility.AddressOf(ref State.Acc); 226 } 227 228 unsafe byte* Buffer 229 { 230 [DebuggerStepThrough] 231 get => (byte*) UnsafeUtility.AddressOf(ref State.Buffer); 232 } 233 234 unsafe byte* SecretKey 235 { 236 [DebuggerStepThrough] 237 get => (byte*) UnsafeUtility.AddressOf(ref State.SecretKey); 238 } 239 240 #endregion 241 242 #region Data storage 243 244 private StreamingStateData State; 245 246 [StructLayout(LayoutKind.Explicit)] 247 struct StreamingStateData 248 { 249 [FieldOffset(0)] public ulong Acc; // 64 bytes 250 [FieldOffset(64)] public byte Buffer; // 256 bytes 251 [FieldOffset(320)] public int IsHash64; // 4 bytes 252 [FieldOffset(324)] public int BufferedSize; // 4 bytes 253 [FieldOffset(328)] public int NbStripesSoFar; // 4 bytes + 4 padding 254 [FieldOffset(336)] public long TotalLength; // 8 bytes 255 [FieldOffset(344)] public ulong Seed; // 8 bytes 256 [FieldOffset(352)] public byte SecretKey; // 192 bytes 257 [FieldOffset(540)] public byte _PadEnd; 258 } 259 260 #endregion 261 262 #region Internals 263 264 private unsafe void DigestLong(ulong* acc, byte* secret, int isHash64) 265 { 266 UnsafeUtility.MemCpy(acc, Acc, STRIPE_LEN); 267 if (State.BufferedSize >= STRIPE_LEN) 268 { 269 var totalNbStripes = (State.BufferedSize - 1) / STRIPE_LEN; 270 ConsumeStripes(acc, ref State.NbStripesSoFar, Buffer, totalNbStripes, secret, isHash64); 271 272 if (X86.Avx2.IsAvx2Supported) 273 { 274 Avx2Accumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null, 275 secret + SECRET_LIMIT - SECRET_LASTACC_START); 276 } 277 else 278 { 279 DefaultAccumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null, 280 secret + SECRET_LIMIT - SECRET_LASTACC_START, isHash64); 281 } 282 } 283 else 284 { 285 var lastStripe = stackalloc byte[STRIPE_LEN]; 286 var catchupSize = STRIPE_LEN - State.BufferedSize; 287 UnsafeUtility.MemCpy(lastStripe, Buffer + INTERNAL_BUFFER_SIZE - catchupSize, catchupSize); 288 UnsafeUtility.MemCpy(lastStripe + catchupSize, Buffer, State.BufferedSize); 289 if (X86.Avx2.IsAvx2Supported) 290 { 291 Avx2Accumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START); 292 } 293 else 294 { 295 DefaultAccumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START, isHash64); 296 } 297 } 298 } 299 300 private unsafe void ConsumeStripes(ulong* acc, ref int nbStripesSoFar, byte* input, long totalStripes, 301 byte* secret, int isHash64) 302 { 303 if (NB_STRIPES_PER_BLOCK - nbStripesSoFar <= totalStripes) 304 { 305 var nbStripes = NB_STRIPES_PER_BLOCK - nbStripesSoFar; 306 if (X86.Avx2.IsAvx2Supported) 307 { 308 Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64); 309 Avx2ScrambleAcc(acc, secret + SECRET_LIMIT); 310 Avx2Accumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64); 311 } 312 else 313 { 314 DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64); 315 DefaultScrambleAcc(acc, secret + SECRET_LIMIT); 316 DefaultAccumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64); 317 } 318 319 nbStripesSoFar = (int) totalStripes - nbStripes; 320 } 321 else 322 { 323 if (X86.Avx2.IsAvx2Supported) 324 { 325 Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64); 326 } 327 else 328 { 329 DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64); 330 } 331 332 nbStripesSoFar += (int) totalStripes; 333 } 334 } 335 336 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 337 void CheckKeySize(int isHash64) 338 { 339 if (State.IsHash64 != isHash64) 340 { 341 var s = State.IsHash64 != 0 ? "64" : "128"; 342 throw new InvalidOperationException( 343 $"The streaming state was create for {s} bits hash key, the calling method doesn't support this key size, please use the appropriate API"); 344 } 345 } 346 347 #endregion 348 } 349 } 350}