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