1 //
2 // Copyright (c) 2010-2022 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Linq;
10 using System.Collections.Generic;
11 using Antmicro.Renode.Exceptions;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Peripherals;
14 using Antmicro.Renode.Core.Structure;
15 using Antmicro.Migrant;
16 using Antmicro.Migrant.Hooks;
17 using Antmicro.Renode.Utilities;
18 
19 namespace Antmicro.Renode.Core
20 {
21     public sealed class ExternalsManager : IHasChildren<IExternal>
22     {
ExternalsManager()23         public ExternalsManager()
24         {
25             externals = new Dictionary<string, IExternal>();
26             registeredIHasOwnLifeObjects = new List<SerializableWeakReference<IHasOwnLife>>();
27             paused = true;
28         }
29 
AddExternal(IExternal external, string name)30         public void AddExternal(IExternal external, string name)
31         {
32             lock(externals)
33             {
34                 if(externals.ContainsValue(external))
35                 {
36                     throw new RecoverableException("External already registered");
37                 }
38                 if(externals.ContainsKey(name))
39                 {
40                     throw new RecoverableException("External's name already in use");
41                 }
42                 externals.Add(name, external);
43             }
44 
45             OnExternalsChanged(external, true);
46         }
47 
48         public IEnumerable<T> GetExternalsOfType<T>() where T : IExternal
49         {
50             return externals.Values.OfType<T>();
51         }
52 
RemoveExternal(IExternal external)53         public void RemoveExternal(IExternal external)
54         {
55             lock(externals)
56             {
57                 if(!externals.ContainsValue(external))
58                 {
59                     throw new RecoverableException("External not registered");
60                 }
61 
62                 externals.Remove(externals.Single(x => x.Value == external).Key);
63             }
64 
65             OnExternalsChanged(external, false);
66         }
67 
Clear()68         public void Clear()
69         {
70             IDisposable[] toDispose;
71             lock(externals)
72             {
73                 toDispose = externals.Select(x => x.Value as IDisposable).Where(x => x != null).ToArray();
74                 externals.Clear();
75             }
76             foreach(var td in toDispose)
77             {
78                 td.Dispose();
79             }
80         }
81 
82         public bool TryGetByName<T>(string name, out T result) where T : class
83         {
84             return TryGetByNameInner(this, name, out result);
85         }
86 
TryGetName(IExternal external, out string name)87         public bool TryGetName(IExternal external, out string name)
88         {
89             lock(externals)
90             {
91                 var result = externals.SingleOrDefault(x => x.Value == external);
92                 if(result.Value != null)
93                 {
94                     name = result.Key;
95                     return true;
96                 }
97 
98                 name = null;
99                 return false;
100             }
101         }
102 
GetNames()103         public IEnumerable<string> GetNames()
104         {
105             lock(externals)
106             {
107                 var result = new List<string>();
108                 var keys = externals.Keys.ToArray();
109                 GetNamesInner(result, string.Empty, keys.Select(x => externals[x]).ToArray(), externals.Keys);
110                 return result;
111             }
112         }
113 
114         public IEnumerable<IExternal> Externals
115         {
116             get
117             {
118                 return externals.Values;
119             }
120         }
121 
Start()122         public void Start()
123         {
124             lock(externals)
125             {
126                 if(!paused)
127                 {
128                     return;
129                 }
130                 paused = false;
131                 var ownLifeExternals = externals.Select(x => x.Value as IHasOwnLife).Where(x => x != null);
132                 if(alreadyStarted)
133                 {
134                     foreach(var external in ownLifeExternals)
135                     {
136                         external.Resume();
137                     }
138                     foreach(var iHasOwnLife in registeredIHasOwnLifeObjects)
139                     {
140                         var target = iHasOwnLife.Target;
141                         if(target != null)
142                         {
143                             target.Resume();
144                         }
145                     }
146                     return;
147                 }
148 
149                 foreach(var external in ownLifeExternals)
150                 {
151                     external.Start();
152                 }
153 
154                 foreach(var iHasOwnLife in registeredIHasOwnLifeObjects)
155                 {
156                     var target = iHasOwnLife.Target;
157                     if(target != null)
158                     {
159                         target.Start();
160                     }
161                 }
162 
163                 alreadyStarted = true;
164             }
165         }
166 
Pause()167         public void Pause()
168         {
169             lock(externals)
170             {
171                 if(paused)
172                 {
173                     return;
174                 }
175                 paused = true;
176                 var ownLifeExternals = externals.Select(x => x.Value as IHasOwnLife).Where(x => x != null);
177                 foreach(var external in ownLifeExternals)
178                 {
179                     external.Pause();
180                 }
181                 foreach(var iHasOwnLife in registeredIHasOwnLifeObjects)
182                 {
183                     var target = iHasOwnLife.Target;
184                     if(target != null)
185                     {
186                         target.Pause();
187                     }
188                 }
189             }
190         }
191 
TryGetByName(string name, out bool success)192         IExternal IHasChildren<IExternal>.TryGetByName(string name, out bool success)
193         {
194             lock(externals)
195             {
196                 if(externals.ContainsKey(name))
197                 {
198                     success = true;
199                     return externals[name];
200                 }
201                 success = false;
202                 return null;
203             }
204         }
205 
GetNamesInner(List<string> result, string prefix, IEnumerable<object> objects, IEnumerable<string> theirNames)206         private void GetNamesInner(List<string> result, string prefix, IEnumerable<object> objects, IEnumerable<string> theirNames)
207         {
208             bool notUsed;
209             var namesEnumerator = theirNames.GetEnumerator();
210             namesEnumerator.MoveNext();
211             foreach(var obj in objects)
212             {
213                 var currentName = string.IsNullOrEmpty(prefix) ? namesEnumerator.Current : prefix + Machine.PathSeparator + namesEnumerator.Current;
214                 result.Add(currentName);
215                 var withChildren = obj as IHasChildren<object>;
216                 if(withChildren != null)
217                 {
218                     var objectsAndNames = withChildren.GetNames().Select(x => new { Name = x, Value = withChildren.TryGetByName(x, out notUsed) })
219                         .Where(x => x.Value != null).ToArray();
220                     GetNamesInner(result, currentName, objectsAndNames.Select(x => x.Value).ToArray(), objectsAndNames.Select(x => x.Name).ToArray());
221                 }
222                 namesEnumerator.MoveNext();
223             }
224         }
225 
226         private bool TryGetByNameInner<T>(IHasChildren<object> currentParent, string subname, out T result) where T : class
227         {
228             if(subname == null)
229             {
230                 result = null;
231                 return false;
232             }
233             var parts = subname.Split(new [] { Machine.PathSeparator } , 2);
234             object candidate;
235             if(!currentParent.TryGetByName(parts[0], out candidate))
236             {
237                 result = null;
238                 return false;
239             }
240             result = candidate as T;
241             if(parts.Length == 1)
242             {
243                 return result != null;
244             }
245             var parent = candidate as IHasChildren<object>;
246             if(parent != null)
247             {
248                 return TryGetByNameInner(parent, parts[1], out result);
249             }
250             return false;
251         }
252 
253         [field: Transient]
254         public event Action<ExternalsChangedEventArgs> ExternalsChanged;
255 
256         private bool alreadyStarted;
257         private bool paused;
258 
259         [Constructor]
260         private readonly Dictionary<string, IExternal> externals;
261 
262         // those fields are used to serialize externals (and skip the transient ones)
263         private List<string> externalsKeys;
264         private List<IExternal> externalsValues;
265 
266         [PreSerialization]
SerializeExternals()267         private void SerializeExternals()
268         {
269             externalsKeys = new List<string>();
270             externalsValues = new List<IExternal>();
271 
272             foreach(var item in externals)
273             {
274                 if(item.Value.GetType().GetCustomAttributes(typeof(TransientAttribute), true).Any())
275                 {
276                     Logger.Log(LogLevel.Info, "Skipping serialization of the '{0}' external as it's marked as transient", item.Key);
277                     continue;
278                 }
279 
280                 externalsKeys.Add(item.Key);
281                 externalsValues.Add(item.Value);
282             }
283         }
284 
285         [PostDeserialization]
RestoreExternals()286         private void RestoreExternals()
287         {
288             for(var i = 0; i < externalsKeys.Count; i++)
289             {
290                 externals.Add(externalsKeys[i], externalsValues[i]);
291             }
292 
293             externalsKeys = null;
294             externalsValues = null;
295         }
296 
RegisterIHasOwnLife(IHasOwnLife own)297         public void RegisterIHasOwnLife(IHasOwnLife own)
298         {
299             lock(externals)
300             {
301                 registeredIHasOwnLifeObjects.Add(new SerializableWeakReference<IHasOwnLife>(own));
302                 if(!paused)
303                 {
304                     own.Start();
305                 }
306             }
307         }
308 
UnregisterIHasOwnLife(IHasOwnLife own)309         public void UnregisterIHasOwnLife(IHasOwnLife own)
310         {
311             lock(externals)
312             {
313                 registeredIHasOwnLifeObjects.RemoveAll(x => x.Target == own);
314                 if(alreadyStarted)
315                 {
316                     own.Pause();
317                 }
318             }
319         }
320 
OnExternalsChanged(IExternal external, bool added)321         private void OnExternalsChanged(IExternal external, bool added)
322         {
323             var ec = ExternalsChanged;
324             if(ec != null)
325             {
326                 ec(new ExternalsChangedEventArgs(external, added ? ExternalsChangedEventArgs.ChangeType.Added : ExternalsChangedEventArgs.ChangeType.Removed));
327             }
328         }
329 
330         private readonly List<SerializableWeakReference<IHasOwnLife>> registeredIHasOwnLifeObjects;
331 
332         public class ExternalsChangedEventArgs : EventArgs
333         {
ExternalsChangedEventArgs(IExternal external, ChangeType change)334             public ExternalsChangedEventArgs(IExternal external, ChangeType change)
335             {
336                 External = external;
337                 Change = change;
338             }
339 
340             public IExternal External { get; private set; }
341             public ChangeType Change { get; private set; }
342 
343             public enum ChangeType
344             {
345                 Added,
346                 Removed
347             }
348         }
349     }
350 }
351 
352