FastRng/FastRng/UIntChannelProducer.cs
2024-05-29 11:39:08 +02:00

121 lines
4.2 KiB
C#

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace FastRng;
internal sealed class UIntChannelProducer : IDisposable
{
#if DEBUG
private const int BUFFER_SIZE = 1_000_000;
#else
private const int BUFFER_SIZE = 1_000_000;
#endif
private readonly Channel<uint> channelIntegers = Channel.CreateBounded<uint>(new BoundedChannelOptions(capacity: BUFFER_SIZE * 2) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = true });
// Gets used to stop the producer thread:
private readonly CancellationTokenSource producerTokenSource = new();
// The uint producer thread:
private Thread producerRandomUint;
// Variable w and z for the uint generator. Both get used
// as seeding variable as well (cf. constructors)
private uint mW;
private uint mZ;
#region Constructors
public UIntChannelProducer()
{
//
// Initialize the mW and mZ by using
// the system's time.
//
var now = DateTime.Now;
var ticks = now.Ticks;
this.mW = (uint) (ticks >> 16);
this.mZ = (uint) (ticks % 4_294_967_296);
this.StartProducerThread();
}
/// <summary>
/// Creates a multithreaded random number generator.
/// </summary>
/// <remarks>
/// A multi-threaded random number generator created by this constructor is
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
///
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
/// the behavior of the consuming application might be non-deterministic. This is possible if
/// the application with multiple threads consumes the numbers. The scheduling of the threads
/// is up to the operating system and might not be predictable.
/// </remarks>
/// <param name="seedU">A seed value to generate a deterministic generator.</param>
public UIntChannelProducer(uint seedU)
{
this.mW = seedU;
this.mZ = 362_436_069;
this.StartProducerThread();
}
/// <summary>
/// Creates a multi-threaded random number generator.
/// </summary>
/// <remarks>
/// A multi-threaded random number generator created by this constructor is
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
///
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
/// the behavior of the consuming application might be non-deterministic. This is possible if
/// the application with multiple threads consumes the numbers. The scheduling of the threads
/// is up to the operating system and might not be predictable.
/// </remarks>
/// <param name="seedU">The first seed value.</param>
/// <param name="seedV">The second seed value.</param>
public UIntChannelProducer(uint seedU, uint seedV)
{
this.mW = seedU;
this.mZ = seedV;
this.StartProducerThread();
}
private void StartProducerThread()
{
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
this.producerRandomUint.Start();
}
#endregion
[ExcludeFromCodeCoverage]
private async void RandomProducerUint(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
await this.channelIntegers.Writer.WriteAsync((this.mZ << 16) + this.mW, cancellationToken);
}
}
catch (OperationCanceledException)
{
}
}
public async ValueTask<uint> GetNextAsync(CancellationToken cancellationToken = default) => await this.channelIntegers.Reader.ReadAsync(cancellationToken);
#region Implementation of IDisposable
private void StopProducer() => this.producerTokenSource.Cancel();
/// <inheritdoc />
public void Dispose() => this.StopProducer();
#endregion
}