DirectOuput Framework R2
DirectOutput framework R2 for virtual pinball cabinets
Go to:
Overview 
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Properties Events Macros Pages
Pinball.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Reflection;
5 using System.Threading;
6 using DirectOutput.Cab;
7 using DirectOutput.FX;
8 using DirectOutput.General;
9 using DirectOutput.GlobalConfiguration;
10 using DirectOutput.LedControl.Loader;
11 using DirectOutput.PinballSupport;
12 using System.Linq;
13 using DirectOutput.Table;
14 using DirectOutput.General.Statistics;
15 using System.Diagnostics;
16 
17 namespace DirectOutput
18 {
23  public class Pinball
24  {
25 
26  #region Properties
27 
28 
29  //public ThreadInfoList ThreadInfoList { get; private set; }
30  public TimeSpanStatisticsList TimeSpanStatistics { get; private set; }
31 
32 
33 
34  private Table.Table _Table = new Table.Table();
35 
42  public Table.Table Table
43  {
44  get { return _Table; }
45  set { _Table = value; }
46  }
47  private Cabinet _Cabinet = new Cabinet();
48 
55  public Cabinet Cabinet
56  {
57  get { return _Cabinet; }
58  set { _Cabinet = value; }
59  }
60 
61 
62 
63  private AlarmHandler _Alarms = new AlarmHandler();
64 
71  public AlarmHandler Alarms
72  {
73  get { return _Alarms; }
74  private set { _Alarms = value; }
75  }
76 
77  private GlobalConfig _GlobalConfig = new GlobalConfig();
78 
86  {
87  get { return _GlobalConfig; }
88  private set { _GlobalConfig = value; }
89  }
90 
91 
92 
93  #endregion
94 
95 
96  #region Init & Finish
97 
105  public void Setup(string GlobalConfigFilename = "", string TableFilename = "", string RomName = "")
106  {
107  bool GlobalConfigLoaded = true;
108  //Load the global config
109 
110 
111  try
112  {
113  if (!GlobalConfigFilename.IsNullOrWhiteSpace())
114  {
115  FileInfo GlobalConfigFile = new FileInfo(GlobalConfigFilename);
116 
117 
118  GlobalConfig = GlobalConfig.GetGlobalConfigFromConfigXmlFile(GlobalConfigFile.FullName);
119  if (GlobalConfig == null)
120  {
121  GlobalConfigLoaded = false;
122 
123  //set new global config object if it config could not be loaded from the file.
124  GlobalConfig = new GlobalConfig();
125  }
126  GlobalConfig.GlobalConfigFilename = GlobalConfigFile.FullName;
127  }
128  else
129  {
130  GlobalConfig = new GlobalConfig();
131  GlobalConfig.GlobalConfigFilename = GlobalConfigFilename;
132  }
133 
134  }
135  catch (Exception E)
136  {
137 
138  throw new Exception("DirectOutput framework could not initialize global config.\n Inner exception: {0}".Build(E.Message), E);
139  }
140 
142  {
144  {
145  try
146  {
147 
148  FileInfo LF = new FileInfo(GlobalConfig.GetLogFilename((!TableFilename.IsNullOrWhiteSpace() ? new FileInfo(TableFilename).FullName : ""), RomName));
149  if (LF.Exists)
150  {
151  LF.Delete();
152  }
153  }
154  catch { }
155  }
156  try
157  {
158 
159  Log.Filename = GlobalConfig.GetLogFilename((!TableFilename.IsNullOrWhiteSpace() ? new FileInfo(TableFilename).FullName : ""), RomName);
160  Log.Init();
161 
162  }
163  catch (Exception E)
164  {
165 
166  throw new Exception("DirectOutput framework could initialize the log file.\n Inner exception: {0}".Build(E.Message), E);
167  }
168  }
169 
170 
171  try
172  {
173  if (GlobalConfigLoaded)
174  {
175  Log.Write("Global config loaded from: {0}".Build(GlobalConfigFilename));
176  }
177  else
178  {
179  if (!GlobalConfigFilename.IsNullOrWhiteSpace())
180  {
181  Log.Write("Could not find or load theGlobalConfig file {0}".Build(GlobalConfigFilename));
182  }
183  else
184  {
185  Log.Write("No GlobalConfig file loaded. Using newly instanciated GlobalConfig object instead.");
186  }
187  }
188 
189 
190 
191  Log.Write("Loading Pinball parts");
192 
193 
194 
195  Log.Write("Loading cabinet");
196  //Load cabinet config
197  Cabinet = null;
198  FileInfo CCF = GlobalConfig.GetCabinetConfigFile();
199  if (CCF != null)
200  {
201  if (CCF.Exists)
202  {
203  Log.Write("Will load cabinet config file: {0}".Build(CCF.FullName));
204  try
205  {
207 
208  Log.Write("{0} output controller defnitions and {1} toy definitions loaded from cabinet config.".Build(Cabinet.OutputControllers.Count,Cabinet.Toys.Count));
209 
210 
211  Cabinet.CabinetConfigurationFilename = CCF.FullName;
213  {
214  Log.Write("Cabinet config file has AutoConfig feature enabled. Calling AutoConfig.");
215  try
216  {
218  }
219  catch (Exception E)
220  {
221  Log.Exception("A eception occured during cabinet auto configuration", E);
222  }
223  Log.Write("Autoconfig complete.");
224  }
225  Log.Write("Cabinet config loaded successfully from {0}".Build(CCF.FullName));
226  }
227  catch (Exception E)
228  {
229  Log.Exception("A exception occured when loading cabinet config file: {0}".Build(CCF.FullName), E);
230 
231 
232  }
233  }
234  else
235  {
236  Log.Warning("Cabinet config file {0} does not exist.".Build(CCF.FullName));
237  }
238  }
239  if (Cabinet == null)
240  {
241  Log.Write("No cabinet config file loaded. Will use AutoConfig.");
242  //default to a new cabinet object if the config cant be loaded
243  Cabinet = new Cabinet();
245  }
246 
247  Log.Write("Cabinet loaded");
248 
249  Log.Write("Loading table config");
250 
251  //Load table config
252 
253  Table = new DirectOutput.Table.Table();
254  Table.AddLedControlConfig = true;
255 
256  if (!TableFilename.IsNullOrWhiteSpace())
257  {
258  FileInfo TableFile = new FileInfo(TableFilename);
259  FileInfo TCF = GlobalConfig.GetTableConfigFile(TableFile.FullName);
260  if (TCF != null)
261  {
262  Log.Write("Will load table config from {0}".Build(TCF.FullName));
263  try
264  {
265  Table = DirectOutput.Table.Table.GetTableFromConfigXmlFile(GlobalConfig.GetTableConfigFile(TableFile.FullName));
266  Table.TableConfigurationFilename = GlobalConfig.GetTableConfigFile(TableFile.FullName).FullName;
267  Log.Write("Table config loaded successfully from {0}".Build(TCF.FullName));
268  }
269  catch (Exception E)
270  {
271  Log.Exception("A exception occured when loading table config: {0}".Build(TCF.FullName), E);
272  }
274  {
275  Log.Write("Table config allows mix with ledcontrol configs.");
276  }
277  }
278  else
279  {
280  Log.Warning("No table config file found. Will try to load config from LedControl file(s).");
281  }
282  }
283  else
284  {
285  Log.Write("No TableFilename specified, will use empty tableconfig");
286  }
288  {
289  if (!RomName.IsNullOrWhiteSpace())
290  {
291  Log.Write("Will try to load configs from DirectOutput.ini or LedControl.ini file(s) for RomName {0}".Build(RomName));
292  //Load ledcontrol
293 
294  Dictionary<int, FileInfo> LedControlIniFiles = GlobalConfig.GetIniFilesDictionary(TableFilename);
295 
296 
298  if (LedControlIniFiles.Count > 0)
299  {
300  L.LoadLedControlFiles(LedControlIniFiles, false);
301  Log.Write("{0} directoutputconfig.ini or ledcontrol.ini files loaded.".Build(LedControlIniFiles.Count));
302  }
303  else
304  {
305  Log.Write("No directoutputconfig.ini or ledcontrol.ini files found.");
306  }
307 
308  if (!L.ContainsConfig(RomName))
309  {
310  Log.Write("No config for table found in LedControl data for RomName {0}.".Build(RomName));
311  }
312  else
313  {
314  Log.Write("Config for RomName {0} exists in LedControl data. Updating cabinet and config.".Build(RomName));
315 
316  DirectOutput.LedControl.Setup.Configurator C = new DirectOutput.LedControl.Setup.Configurator();
317  C.EffectMinDurationMs = GlobalConfig.LedControlMinimumEffectDurationMs;
318  C.EffectRGBMinDurationMs = GlobalConfig.LedControlMinimumRGBEffectDurationMs;
319  C.Setup(L, Table, Cabinet, RomName);
320  C = null;
321  // L.UpdateTableConfig(Table, RomName, Cabinet);
322 
323  //Check DOF Version
324  Version DOFVersion = typeof(Pinball).Assembly.GetName().Version;
325 
326  if(L.Any(LC=>LC.MinDOFVersion!=null && LC.MinDOFVersion.CompareTo(DOFVersion)>0)) {
327 
328  Version MaxVersion = null;
329  foreach (LedControlConfig LC in L)
330  {
331  if(LC.MinDOFVersion!=null && (MaxVersion==null || MaxVersion.CompareTo(LC.MinDOFVersion)>0)) {
332  MaxVersion = LC.MinDOFVersion;
333  }
334  }
335 
336 
337  Log.Warning("UPDATE DIRECT OUTPUT FRAMEWORK!");
338  if (MaxVersion != null)
339  {
340  Log.Warning("Current DOF version is {0}, but DOF version {1} or later is required by one or several config files.".Build(DOFVersion, MaxVersion));
341  }
342  try
343  {
344 
345  Process.Start(Path.Combine(Path.GetDirectoryName( System.Reflection.Assembly.GetExecutingAssembly().Location),"UpdateNotification.exe"));
346  }
347  catch (Exception E)
348  {
349  Log.Exception("A exception occured when displaying the update notification", E);
350  }
351  }
352 
353 
354 
355 
356  }
357  L = null;
358  }
359  else
360  {
361 
362  Log.Write("Cant load config from directoutput.ini or ledcontrol.ini file(s) since no RomName was supplied. No ledcontrol config will be loaded.");
363  }
364 
365  }
366  if (Table.TableName.IsNullOrWhiteSpace())
367  {
368  if (!TableFilename.IsNullOrWhiteSpace())
369  {
370  Table.TableName = Path.GetFileNameWithoutExtension(new FileInfo(TableFilename).FullName);
371  }
372  else if (!RomName.IsNullOrWhiteSpace())
373  {
374  Table.TableName = RomName;
375  }
376  }
377  if (!TableFilename.IsNullOrWhiteSpace())
378  {
379  Table.TableFilename = new FileInfo(TableFilename).FullName;
380  }
381  if (!RomName.IsNullOrWhiteSpace())
382  {
383  Table.RomName = RomName;
384  }
385  Log.Write("Table config loading finished");
386 
387 
388 
389  Log.Write("Pinball parts loaded");
390  }
391  catch (Exception E)
392  {
393  Log.Exception("DirectOutput framework has encountered a exception during setup.", E);
394  throw new Exception("DirectOutput framework has encountered a exception during setup.\n Inner exception: {0}".Build(E.Message), E);
395  }
396  }
397 
401  public void Init()
402  {
403 
404  try
405  {
406 
407  Log.Write("Starting processes");
408  InitStatistics();
409  Cabinet.Init(this);
410  Table.Init(this);
411  Alarms.Init(this);
413  Cabinet.Update();
414 
415  //Add the thread initializing the framework to the threadinfo list
416  ThreadInfo TI = new ThreadInfo(Thread.CurrentThread);
417  TI.HeartBeatTimeOutMs = 10000;
418  TI.HostName = "External caller";
419  TI.HeartBeat();
420  //ThreadInfoList.Add(TI);
421 
422 
423 
424  InitMainThread();
425  Log.Write("Framework initialized.");
426  Log.Write("Have fun! :)");
427 
428 
429  }
430  catch (Exception E)
431  {
432  Log.Exception("DirectOutput framework has encountered a exception during initialization.", E);
433  throw new Exception("DirectOutput framework has encountered a exception during initialization.\n Inner exception: {0}".Build(E.Message), E);
434  }
435  }
436 
440  public void Finish()
441  {
442  try
443  {
444  Log.Write("Finishing framework");
445  FinishMainThread();
446 
447  Alarms.Finish();
448  Table.Finish();
449  Cabinet.Finish();
450 
451 
452  // WriteStatisticsToLog();
453 
454  //ThreadInfoList.ThreadTerminates();
455 
456  Log.Write("DirectOutput framework finished.");
457  Log.Write("Bye and thanks for using!");
458 
459  }
460  catch (Exception E)
461  {
462  Log.Exception("A exception occured while finishing the DirectOutput framework.", E);
463  throw new Exception("DirectOutput framework has encountered while finishing.\n Inner exception: {0}".Build(E.Message), E);
464  }
465  }
466 
467  #endregion
468 
469 
470 
471  #region MainThread
472 
473 
474 
475 
476  private void InitMainThread()
477  {
478 
479  if (!MainThreadIsActive)
480  {
481  KeepMainThreadAlive = true;
482  try
483  {
484  MainThread = new Thread(MainThreadDoIt);
485  MainThread.Name = "DirectOutput MainThread ";
486  MainThread.Start();
487 
488 
489  }
490  catch (Exception E)
491  {
492  Log.Exception("DirectOutput MainThread could not start.", E);
493  throw new Exception("DirectOutput MainThread could not start.", E);
494  }
495  }
496  }
497 
502  private void FinishMainThread()
503  {
504  if (MainThread != null)
505  {
506  try
507  {
508  KeepMainThreadAlive = false;
509  lock (MainThreadLocker)
510  {
511  Monitor.Pulse(MainThreadLocker);
512  }
513  if (!MainThread.Join(1000))
514  {
515  MainThread.Abort();
516  }
517  MainThread = null;
518  }
519  catch (Exception E)
520  {
521  Log.Exception("A error occured during termination of DirectOutput MainThread", E);
522  throw new Exception("A error occured during termination of DirectOutput MainThread", E);
523  }
524  }
525  }
526 
527 
531  public bool MainThreadIsActive
532  {
533  get
534  {
535  if (MainThread != null)
536  {
537  if (MainThread.IsAlive)
538  {
539  return true;
540  }
541  }
542  return false;
543  }
544  }
545 
549  public void MainThreadSignal()
550  {
551  lock (MainThreadLocker)
552  {
553  MainThreadDoWork = true;
554  Monitor.Pulse(MainThreadLocker);
555  }
556  }
557 
558 
559  private Thread MainThread { get; set; }
560  private object MainThreadLocker = new object();
561  private bool KeepMainThreadAlive = true;
562  private bool MainThreadDoWork = false;
563  //TODO: Maybe this should be a config option
564  const int MaxInputDataProcessingTimeMs = 10;
565 
566 
571  //TODO: Think about implement something which does really check on value changes on tableelements or triggered effects before setting update required.
572  private void MainThreadDoIt()
573  {
574 
575  //ThreadInfoList.HeartBeat("DirectOutput");
576  try
577  {
578  while (KeepMainThreadAlive)
579  {
580  bool UpdateRequired = false;
581  DateTime Start = DateTime.Now;
582 
583  //Consume the tableelement data delivered from the calling application
584  while (InputQueue.Count > 0 && (DateTime.Now - Start).Milliseconds <= MaxInputDataProcessingTimeMs && KeepMainThreadAlive)
585  {
587 
588  D = InputQueue.Dequeue();
589  try
590  {
591  DateTime StartTime = DateTime.Now;
593  UpdateRequired |= true;
594  UpdateTableElementStatistics(D, (DateTime.Now - StartTime));
595  }
596  catch (Exception E)
597  {
598  Log.Exception("A unhandled exception occured while processing data for table element {0} {1} with value {2}".Build(D.TableElementType, D.Number, D.Value), E);
599  //ThreadInfoList.RecordException(E);
600 
601  }
602  }
603 
604  if (KeepMainThreadAlive)
605  {
606  try
607  {
608  //Executed all alarms which have been scheduled for the current time
609  UpdateRequired |= Alarms.ExecuteAlarms(DateTime.Now.AddMilliseconds(1));
610  }
611  catch (Exception E)
612  {
613  Log.Exception("A unhandled exception occured while executing timer events.", E);
614  //ThreadInfoList.RecordException(E);
615  }
616  }
617 
618 
619  //Call update on output controllers if necessary
620  if (UpdateRequired && KeepMainThreadAlive)
621  {
622  try
623  {
624  Cabinet.Update();
625  }
626  catch (Exception E)
627  {
628  Log.Exception("A unhandled exception occured while updating the output controllers", E);
629  //ThreadInfoList.RecordException(E);
630  }
631  }
632 
633  if (KeepMainThreadAlive)
634  {
635  //ThreadInfoList.HeartBeat();
636  //Sleep until we get more input data and/or a timer expires.
637  DateTime NextAlarm = Alarms.GetNextAlarmTime();
638 
639  lock (MainThreadLocker)
640  {
641  while (InputQueue.Count == 0 && NextAlarm > DateTime.Now && !MainThreadDoWork && KeepMainThreadAlive)
642  {
643  int TimeOut = ((int)(NextAlarm - DateTime.Now).TotalMilliseconds).Limit(1, 50);
644 
645  Monitor.Wait(MainThreadLocker, TimeOut); // Lock is released while we’re waiting
646  //ThreadInfoList.HeartBeat();
647  }
648  }
649  MainThreadDoWork = false;
650  }
651 
652 
653  }
654  }
655  catch (Exception E)
656  {
657  Log.Exception("A unexpected exception occured in the DirectOutput MainThread", E);
658  //ThreadInfoList.RecordException(E);
659  }
660 
661  //ThreadInfoList.ThreadTerminates();
662  }
663  #endregion
664 
665 
666  private Dictionary<TableElementTypeEnum, TimeSpanStatisticsItem> TableElementCallStatistics = new Dictionary<TableElementTypeEnum, TimeSpanStatisticsItem>();
667 
668 
669  private void InitStatistics()
670  {
671  Log.Debug("Initializing table element statistics");
674 
675  TableElementCallStatistics = new Dictionary<TableElementTypeEnum, TimeSpanStatisticsItem>();
676  foreach (TableElementTypeEnum T in Enum.GetValues(typeof(TableElementTypeEnum)))
677  {
678  TSI = new TimeSpanStatisticsItem() { Name = "{0}".Build(T.ToString()), GroupName = "Pinball - Table element update calls" };
679  TableElementCallStatistics.Add(T, TSI);
680  TimeSpanStatistics.Add(TSI);
681  }
682 
683 
684 
685  Log.Debug("Table element statistics initialized");
686 
687  }
688 
695  {
696  try
697  {
698  TableElementCallStatistics[TableElementData.TableElementType].AddDuration(Duration);
699  }
700  catch (Exception E)
701  {
702  Log.Exception("Could not update TimeSpanStatistics for Pinball table element type {0} ({1})".Build(TableElementData.ToString(), TableElementData), E);
703  }
704  }
705 
706 
710  public void WriteStatisticsToLog()
711  {
712  Log.Write("Duration statistics:");
713 
715  string LastGroupName = "";
717  {
718  if (LastGroupName != TSI.GroupName)
719  {
720  Log.Write(" {0}".Build(TSI.GroupName));
721  LastGroupName = TSI.GroupName;
722  }
723  Log.Write(" - {0}".Build(TSI.ToString()));
724 
725  }
726 
727 
728 
729  }
730 
731  private InputQueue InputQueue = new InputQueue();
732 
740  public void ReceiveData(char TableElementTypeChar, int Number, int Value)
741  {
742  InputQueue.Enqueue(TableElementTypeChar, Number, Value);
744  //ThreadInfoList.HeartBeat("Data delivery");
745 
746  }
747 
754  {
755  InputQueue.Enqueue(TableElementData);
757  //ThreadInfoList.HeartBeat("Data delivery");
758  }
759 
760 
761 
762  #region ToString
763 
764 
765 
766 
767 
768 
769  public override string ToString()
770  {
771  string S = this.GetType().FullName + " {\n";
772  S += " GlobalConfig {\n";
773  S += " Global Config filename:" + GlobalConfig.GlobalConfigFilename + "\n";
774  S += " }\n";
775  S += " Table {\n";
776  S += " Tablename: " + Table.TableName + "\n";
777  S += " Tablefileename: " + Table.TableFilename + "\n";
778  S += " RomName: " + Table.RomName + "\n";
779  S += " Table config source: " + Table.ConfigurationSource + "\n";
780  S += " Table config fileename: " + Table.TableConfigurationFilename + "\n";
781  S += " Table Elements count: " + Table.TableElements.Count + "\n";
782  S += " Table Effects count: " + Table.Effects.Count + "\n";
783  S += " }\n";
784  S += " Cabinet {\n";
785  S += " Cabinet config filename: " + Cabinet.CabinetConfigurationFilename + "\n";
786  S += " Outputcontrollers count: " + Cabinet.OutputControllers.Count + "\n";
787  S += " Output toys count: " + Cabinet.Toys.Count + "\n";
788  S += " }\n";
789 
790  S += "}\n";
791  return S;
792 
793  }
794  #endregion
795 
796 
797  #region Constructor
798 
799 
800 
801  public Pinball()
802  {
803 
804  // ThreadInfoList = new ThreadInfoList();
806 
807  }
808 
809 
816  //public Pinball(string GlobalConfigFilename = "", string TableFilename = "", string RomName = "")
817  // : this()
818  //{
819  // Init(GlobalConfigFilename, TableFilename, RomName);
820  //}
821  #endregion
822 
823 
824  }
825 }