Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/Emulator/Main/Core/PseudorandomNumberGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public void ResetSeed(int newSeed)
{
Logger.Log(LogLevel.Warning, "Pseudorandom Number Generator has already been used with seed {0}. Next time it will use a new one {1}. It won't be possible to repeat this exact execution.", baseSeed, newSeed);
InitializeGenerator();
generatorsByName.Clear();
}
baseSeed = newSeed;
}
Expand Down Expand Up @@ -83,11 +84,23 @@ public void GetItems<T>(ReadOnlySpan<T> choices, Span<T> destination)

private RandomGenerator GetGeneratorForCurentThread()
{
return new RandomGenerator
// The per-thread generator must be keyed by the logical thread *name*, not by the physical
// managed-thread identity that ThreadLocal uses. Some threads (e.g. emulated CPU cores) are
// torn down and recreated under the same name during a run. Creating a brand-new Random for
// each physical thread would re-seed it identically (the seed derives only from the name and
// baseSeed), restarting the pseudo-random stream from the beginning every time. By caching the
// generator by name we let a recreated thread continue its existing, deterministic stream.
var name = Thread.CurrentThread.Name;
if(!generatorsByName.TryGetValue(name, out var randomGenerator))
{
Random = new Random(GetSeedForThread()),
ThreadName = Thread.CurrentThread.Name
};
randomGenerator = new RandomGenerator
{
Random = new Random(GetSeedForThread()),
ThreadName = name
};
generatorsByName.Add(name, randomGenerator);
}
return randomGenerator;
}

private int GetSeedForThread()
Expand Down Expand Up @@ -146,6 +159,9 @@ private void InitializeGenerator()
private ThreadLocal<RandomGenerator> generator;

private readonly HashSet<RandomGenerator> serializedGenerators = new HashSet<RandomGenerator>(); // we initialize the collection to simplify the rest of the code
// Generators kept alive by logical thread name so a thread recreated under the same name continues
// its stream instead of restarting it. Outlives the physical threads tracked by `generator`.
private readonly Dictionary<string, RandomGenerator> generatorsByName = new Dictionary<string, RandomGenerator>();
private readonly object locker;

private class RandomGenerator
Expand Down