1 // 2 // Copyright (c) 2010-2018 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Linq; 10 using System.Text; 11 using System.Threading; 12 using Antmicro.Migrant; 13 using Antmicro.Migrant.Hooks; 14 using Antmicro.Renode.Logging; 15 16 namespace Antmicro.Renode.Core 17 { 18 public sealed class PseudorandomNumberGenerator 19 { PseudorandomNumberGenerator()20 public PseudorandomNumberGenerator() 21 { 22 locker = new object(); 23 InitializeGenerator(); 24 } 25 ResetSeed(int newSeed)26 public void ResetSeed(int newSeed) 27 { 28 lock(locker) 29 { 30 if(generator.Values.Count != 0) 31 { 32 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); 33 InitializeGenerator(); 34 } 35 baseSeed = newSeed; 36 } 37 } 38 GetCurrentSeed()39 public int GetCurrentSeed() 40 { 41 return baseSeed; 42 } 43 NextDouble()44 public double NextDouble() 45 { 46 return GetOrCreateGenerator().NextDouble(); 47 } 48 Next()49 public int Next() 50 { 51 return GetOrCreateGenerator().Next(); 52 } 53 Next(int maxValue)54 public int Next(int maxValue) 55 { 56 return GetOrCreateGenerator().Next(maxValue); 57 } 58 Next(int minValue, int maxValue)59 public int Next(int minValue, int maxValue) 60 { 61 return GetOrCreateGenerator().Next(minValue, maxValue); 62 } 63 NextBytes(byte[] buffer)64 public void NextBytes(byte[] buffer) 65 { 66 GetOrCreateGenerator().NextBytes(buffer); 67 } 68 GetGeneratorForCurentThread()69 private RandomGenerator GetGeneratorForCurentThread() 70 { 71 return new RandomGenerator 72 { 73 Random = new Random(GetSeedForThread()), 74 ThreadName = Thread.CurrentThread.Name 75 }; 76 } 77 GetSeedForThread()78 private int GetSeedForThread() 79 { 80 if(Thread.CurrentThread.IsThreadPoolThread) 81 { 82 throw new InvalidOperationException($"Cannot access {typeof(PseudorandomNumberGenerator)} from a thread pool."); 83 } 84 var name = Thread.CurrentThread.Name; 85 if(string.IsNullOrEmpty(name)) 86 { 87 throw new InvalidOperationException($"Cannot access {typeof(PseudorandomNumberGenerator)} from an unnamed thread."); 88 } 89 90 return Encoding.UTF8.GetBytes(name).Sum(x => (int)x) ^ baseSeed; 91 } 92 GetOrCreateGenerator()93 private Random GetOrCreateGenerator() 94 { 95 lock(locker) 96 { 97 if(generator.Values.Count == 0 && serializedGenerators.Count == 0) 98 { 99 Logger.Log(LogLevel.Info, "Pseudorandom Number Generator was created with seed: {0}", baseSeed); 100 } 101 if(!generator.IsValueCreated && serializedGenerators.Count > 0) 102 { 103 var possibleGenerator = serializedGenerators.FirstOrDefault(x => x.ThreadName == Thread.CurrentThread.Name); 104 if(possibleGenerator != null) 105 { 106 //this is a small optimization so we don't fall in this `if` after we deserialize all entries 107 serializedGenerators.Remove(possibleGenerator); 108 generator.Value = possibleGenerator; 109 } 110 } 111 return generator.Value.Random; 112 } 113 } 114 115 [PreSerialization] BeforeSerialization()116 private void BeforeSerialization() 117 { 118 foreach(var randomGenerator in generator.Values) 119 { 120 serializedGenerators.Add(randomGenerator); 121 } 122 } 123 124 [PostDeserialization] InitializeGenerator()125 private void InitializeGenerator() 126 { 127 generator = new ThreadLocal<RandomGenerator>(() => GetGeneratorForCurentThread(), true); 128 } 129 130 [Transient] 131 private ThreadLocal<RandomGenerator> generator; 132 133 private readonly HashSet<RandomGenerator> serializedGenerators = new HashSet<RandomGenerator>(); // we initialize the collection to simplify the rest of the code 134 private readonly object locker; 135 136 private static int baseSeed = new Random().Next(); 137 138 private class RandomGenerator 139 { 140 public Random Random; 141 public string ThreadName; 142 } 143 } 144 } 145