mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-15 15:09:29 +00:00
Refactor FastRandom class with thread-safe shared instance (#986)
- Restructure FastRandom to use interface-based implementation approach - Add thread-safe Shared static instance for concurrent access - Enhance documentation with improved explanations about algorithm characteristics - Extract core logic into LinearCongruentialGeneratorImpl for better maintainability - Replace Math function calls with MathF equivalents for better performance - Add parameter validation using new .NET ArgumentOutOfRangeException.ThrowIfNegativeOrZero - Keep API compatibility with original implementation
This commit is contained in:
committed by
GitHub
parent
f856694801
commit
b88e0d4ab1
@@ -1,127 +1,342 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MonoGame.Extended
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A random number generator that uses a fast algorithm to generate random values.
|
||||
/// The speed comes at the price of true 'randomness' though, there are noticeable
|
||||
/// patterns & it compares quite unfavourably to other algorithms in that respect.
|
||||
/// It's a good choice in situations where speed is more desirable than a
|
||||
/// good random distribution, and a poor choice when random distribution is important.
|
||||
/// Represents a pseudo-random number generator using a linear congruential generator algorithm.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This implementation uses the same constants as Microsoft Visual C++ rand() function:
|
||||
///
|
||||
/// a=214013
|
||||
/// c=2531011
|
||||
/// m=2^31
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It provides high performance and speed, but comes at the price of having lower statistical quality, or true
|
||||
/// 'randomness' compared to modern algorithms. The algorithm is deterministic based on the initial seed
|
||||
/// value, making it suitable for reproducible sequences.
|
||||
///</para>
|
||||
///<para>
|
||||
/// Note: This pseudo-random number generator exhibits noticeable patterns and should not be used for
|
||||
/// cryptographic purposes or when a high-quality random distribution is critical. Consider using
|
||||
/// <see cref="System.Random"/> for better statistical properties.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class FastRandom
|
||||
{
|
||||
private int _state;
|
||||
private readonly IFastRandomImpl _impl;
|
||||
|
||||
public FastRandom()
|
||||
: this(1)
|
||||
/// <summary>
|
||||
/// Provides a thread-safe <see cref="FastRandom"/> instance that may be used concurrently from any thread.
|
||||
/// </summary>
|
||||
public static FastRandom Shared { get; } = new FastRandom(new ThreadSafeFastRandomImpl());
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FastRandom"/> class using the default seed value.
|
||||
/// </summary>
|
||||
public FastRandom() : this(1)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FastRandom"/> class using the specified seed value.
|
||||
/// </summary>
|
||||
/// <param name="seed">A number used to calculate a starting value for the pseudo-random number sequence.</param>
|
||||
public FastRandom(int seed)
|
||||
{
|
||||
if (seed < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(seed), "seed must be greater than zero");
|
||||
_impl = new LinearCongruentialGeneratorImpl(seed);
|
||||
}
|
||||
|
||||
_state = seed;
|
||||
private FastRandom(IFastRandomImpl impl)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(impl);
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random integer value.
|
||||
/// Returns a non-negative random integer.
|
||||
/// </summary>
|
||||
/// <returns>A random positive integer.</returns>
|
||||
/// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than 32768.</returns>
|
||||
public int Next()
|
||||
{
|
||||
_state = 214013*_state + 2531011;
|
||||
return (_state >> 16) & 0x7FFF;
|
||||
return _impl.Next();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random integer value which is greater than zero and less than or equal to
|
||||
/// the specified maxmimum value.
|
||||
/// Returns a non-negative random integer that is less than or equal to the specified maximum.
|
||||
/// </summary>
|
||||
/// <param name="max">The maximum random integer value to return.</param>
|
||||
/// <returns>A random integer value between zero and the specified maximum value.</returns>
|
||||
/// <param name="max">The inclusive upper bound of the random number to be generated.</param>
|
||||
/// <returns>
|
||||
/// A 32-bit signed integer that is greater than or equal to 0 and less than or equal to <paramref name="max"/>.
|
||||
/// </returns>
|
||||
public int Next(int max)
|
||||
{
|
||||
return (int) (max*NextSingle() + 0.5f);
|
||||
return _impl.Next(max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random integer between the specified minimum and maximum values.
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="min">The inclusive minimum value.</param>
|
||||
/// <param name="max">The inclusive maximum value.</param>
|
||||
/// <param name="min">The inclusive lower bound of the random number returned.</param>
|
||||
/// <param name="max">The inclusive upper bound of the random number returned.</param>
|
||||
/// <returns>
|
||||
/// A 32-bit signed integer that is greater than or equal to <paramref name="min"/> and less than or equal to
|
||||
/// <paramref name="max"/>.
|
||||
/// </returns>
|
||||
public int Next(int min, int max)
|
||||
{
|
||||
return (int) ((max - min)*NextSingle() + 0.5f) + min;
|
||||
return _impl.Next(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random integer between the specified range of values.
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="range">A range representing the inclusive minimum and maximum values.</param>
|
||||
/// <returns>A random integer between the specified minumum and maximum values.</returns>
|
||||
/// <param name="range">
|
||||
/// A range representing the inclusive lower and upper bound of the random number to return.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A 32-bit signed integer that is greater than or equal to the <see cref="Range{T}.Min"/> and less than or
|
||||
/// equal to the <see cref="Range{T}.Max"/> value of <paramref name="range"/>.
|
||||
/// </returns>
|
||||
public int Next(Range<int> range)
|
||||
{
|
||||
return Next(range.Min, range.Max);
|
||||
return _impl.Next(range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random single value.
|
||||
/// Returns a random floating-point number that is greater than or equal to 0.0 and less than 1.0.
|
||||
/// </summary>
|
||||
/// <returns>A random single value between 0 and 1.</returns>
|
||||
/// <returns>
|
||||
/// A single-precision floating point number that is greater than or equal to 0.0 and less than 1.0.
|
||||
/// </returns>
|
||||
public float NextSingle()
|
||||
{
|
||||
return Next()/(float) short.MaxValue;
|
||||
return _impl.NextSingle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random single value which is greater than zero and less than or equal to
|
||||
/// the specified maxmimum value.
|
||||
/// Returns a random floating-point number that is greater than or equal to 0.0 and less than the
|
||||
/// specified maximum.
|
||||
/// </summary>
|
||||
/// <param name="max">The maximum random single value to return.</param>
|
||||
/// <returns>A random single value between zero and the specified maximum value.</returns>
|
||||
/// <param name="max">The exclusive upper bond of the random number generated.</param>
|
||||
/// <returns>
|
||||
/// A single precision floating-point number that is greater than or equal to 0.0 and less than
|
||||
/// <paramref name="max"/>.
|
||||
/// </returns>
|
||||
public float NextSingle(float max)
|
||||
{
|
||||
return max*NextSingle();
|
||||
return _impl.NextSingle(max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random single value between the specified minimum and maximum values.
|
||||
/// Returns a random floating-point number that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="min">The inclusive minimum value.</param>
|
||||
/// <param name="max">The inclusive maximum value.</param>
|
||||
/// <returns>A random single value between the specified minimum and maximum values.</returns>
|
||||
/// <param name="min">The inclusive lower bound of the random number returned.</param>
|
||||
/// <param name="max">The exclusive upper bound of the random number returned.</param>
|
||||
/// <returns>
|
||||
/// A single-precision floating point number that is greater than or equal to <paramref name="min"/> and
|
||||
/// less than <paramref name="max"/>.
|
||||
/// </returns>
|
||||
public float NextSingle(float min, float max)
|
||||
{
|
||||
return (max - min)*NextSingle() + min;
|
||||
return _impl.NextSingle(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random single value between the specified range of values.
|
||||
/// Returns a random floating-point number that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="range">A range representing the inclusive minimum and maximum values.</param>
|
||||
/// <returns>A random single value between the specified minimum and maximum values.</returns>
|
||||
/// <param name="range">
|
||||
/// A range representing the inclusive lower and exclusive upper bound of the random number returned.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A single-precision floating point number that is greater than or equal to the <see cref="Range{T}.Min"/>
|
||||
/// and less than the <see cref="Range{T}.Max"/> value of <paramref name="range"/>
|
||||
/// </returns>
|
||||
public float NextSingle(Range<float> range)
|
||||
{
|
||||
return NextSingle(range.Min, range.Max);
|
||||
return _impl.NextSingle(range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next random angle value.
|
||||
/// Returns a random angle between -π and π.
|
||||
/// </summary>
|
||||
/// <returns>A random angle value.</returns>
|
||||
/// <returns>
|
||||
/// A random angle value in radians.
|
||||
/// </returns>
|
||||
public float NextAngle()
|
||||
{
|
||||
return NextSingle(-MathHelper.Pi, MathHelper.Pi);
|
||||
return _impl.NextAngle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random unit vector.
|
||||
/// </summary>
|
||||
/// <param name="vector">When this method returns, contains a unit vector with a random direction.</param>
|
||||
public void NextUnitVector(out Vector2 vector)
|
||||
{
|
||||
var angle = NextAngle();
|
||||
vector = new Vector2((float) Math.Cos(angle), (float) Math.Sin(angle));
|
||||
_impl.NextUnitVector(out vector);
|
||||
}
|
||||
|
||||
#region IFastRandomImplementation
|
||||
private interface IFastRandomImpl
|
||||
{
|
||||
int Next();
|
||||
int Next(int max);
|
||||
int Next(int min, int max);
|
||||
int Next(Range<int> range);
|
||||
float NextSingle();
|
||||
float NextSingle(float max);
|
||||
float NextSingle(float min, float max);
|
||||
float NextSingle(Range<float> range);
|
||||
float NextAngle();
|
||||
void NextUnitVector(out Vector2 vector);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Linear Congruential Generator
|
||||
private sealed class LinearCongruentialGeneratorImpl : IFastRandomImpl
|
||||
{
|
||||
private const int MULTIPLIER = 214013;
|
||||
private const int INCREMENT = 2531011;
|
||||
|
||||
private int _state;
|
||||
|
||||
public LinearCongruentialGeneratorImpl() : this(1)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LinearCongruentialGeneratorImpl(int seed)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(seed);
|
||||
_state = seed;
|
||||
}
|
||||
|
||||
public int Next()
|
||||
{
|
||||
_state = MULTIPLIER * _state + INCREMENT;
|
||||
return (_state >> 16) & 0x7FFF;
|
||||
}
|
||||
|
||||
public int Next(int max)
|
||||
{
|
||||
return (int)(max * NextSingle() + 0.5f);
|
||||
}
|
||||
|
||||
public int Next(int min, int max)
|
||||
{
|
||||
return (int)((max - min) * NextSingle() + 0.5f) + min;
|
||||
}
|
||||
|
||||
public int Next(Range<int> range)
|
||||
{
|
||||
return Next(range.Min, range.Max);
|
||||
}
|
||||
|
||||
public float NextSingle()
|
||||
{
|
||||
return Next() / (float)short.MaxValue;
|
||||
}
|
||||
|
||||
public float NextSingle(float max)
|
||||
{
|
||||
return max * NextSingle();
|
||||
}
|
||||
|
||||
public float NextSingle(float min, float max)
|
||||
{
|
||||
return (max - min) * NextSingle() + min;
|
||||
}
|
||||
|
||||
public float NextSingle(Range<float> range)
|
||||
{
|
||||
return NextSingle(range.Min, range.Max);
|
||||
}
|
||||
|
||||
public float NextAngle()
|
||||
{
|
||||
return NextSingle(-MathHelper.Pi, MathHelper.Pi);
|
||||
}
|
||||
|
||||
public void NextUnitVector(out Vector2 vector)
|
||||
{
|
||||
float angle = NextAngle();
|
||||
vector.X = MathF.Cos(angle);
|
||||
vector.Y = MathF.Sin(angle);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ThreadSafeImpl
|
||||
private sealed class ThreadSafeFastRandomImpl : IFastRandomImpl
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static LinearCongruentialGeneratorImpl t_random;
|
||||
|
||||
private static LinearCongruentialGeneratorImpl LocalRandom => t_random ?? Create();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static LinearCongruentialGeneratorImpl Create()
|
||||
{
|
||||
t_random = new LinearCongruentialGeneratorImpl();
|
||||
return t_random;
|
||||
}
|
||||
|
||||
public int Next()
|
||||
{
|
||||
return LocalRandom.Next();
|
||||
}
|
||||
|
||||
public int Next(int max)
|
||||
{
|
||||
return LocalRandom.Next(max);
|
||||
}
|
||||
|
||||
public int Next(int min, int max)
|
||||
{
|
||||
return LocalRandom.Next(min, max);
|
||||
}
|
||||
|
||||
public int Next(Range<int> range)
|
||||
{
|
||||
return LocalRandom.Next(range.Min, range.Max);
|
||||
}
|
||||
public float NextSingle()
|
||||
{
|
||||
return LocalRandom.NextSingle();
|
||||
}
|
||||
|
||||
public float NextSingle(float max)
|
||||
{
|
||||
return LocalRandom.NextSingle(max);
|
||||
}
|
||||
|
||||
public float NextSingle(float min, float max)
|
||||
{
|
||||
return LocalRandom.NextSingle(min, max);
|
||||
}
|
||||
|
||||
public float NextSingle(Range<float> range)
|
||||
{
|
||||
return LocalRandom.NextSingle(range.Min, range.Max);
|
||||
}
|
||||
|
||||
public float NextAngle()
|
||||
{
|
||||
return LocalRandom.NextAngle();
|
||||
}
|
||||
|
||||
public void NextUnitVector(out Vector2 vector)
|
||||
{
|
||||
LocalRandom.NextUnitVector(out vector);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user