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
PacLed64.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Runtime.InteropServices;
6 using System.Threading;
7 using System.Xml.Serialization;
8 using DirectOutput.General;
9 using DirectOutput.General.Statistics;
10 
11 namespace DirectOutput.Cab.Out.Pac
12 {
25  {
26  #region Id
27 
28 
29  private object IdUpdateLocker = new object();
30  private int _Id = -1;
31 
43  public int Id
44  {
45  get { return _Id; }
46  set
47  {
48  if (!value.IsBetween(1, 4))
49  {
50  throw new Exception("PacLed64 Ids must be between 1-4. The supplied Id {0} is out of range.".Build(value));
51  }
52  lock (IdUpdateLocker)
53  {
54  if (_Id != value)
55  {
56 
57  if (Name.IsNullOrWhiteSpace() || Name == "PacLed64 {0:0}".Build(_Id))
58  {
59  Name = "PacLed64 {0:0}".Build(value);
60  }
61 
62  _Id = value;
63 
64  }
65  }
66  }
67  }
68 
69  #endregion
70 
71 
72 
73  #region IOutputcontroller implementation
74 
75 
76 
77  public override void Update()
78  {
79  PacLed64Units[Id].TriggerPacLed64UpdaterThread();
80  }
81 
82 
89  public override void Init(Cabinet Cabinet)
90  {
91  AddOutputs();
92  PacLed64Units[Id].Init(Cabinet);
93  Log.Write("PacLed64 Id:{0} initialized and updater thread started.".Build(Id));
94 
95  }
96 
101  public override void Finish()
102  {
103  PacLed64Units[Id].Finish();
104  PacLed64Units[Id].ShutdownLighting();
105  Log.Write("PacLed64 Id:{0} finished and updater thread stopped.".Build(Id));
106 
107  }
108  #endregion
109 
110 
111 
112  #region Outputs
113 
114 
119  private void AddOutputs()
120  {
121  for (int i = 1; i <= 64; i++)
122  {
123  if (!Outputs.Any(x => ((OutputNumbered)x).Number == i))
124  {
125  Outputs.Add(new OutputNumbered() { Name = "{0}.{1:00}".Build(Name, i), Number = i });
126  }
127  }
128  }
129 
130 
131 
132 
143  protected override void OnOutputValueChanged(IOutput Output)
144  {
145 
146  if (!(Output is OutputNumbered))
147  {
148  throw new Exception("The OutputValueChanged event handler for PacLed64 unit {0} (Id: {2:0}) has been called by a sender which is not a OutputNumbered.".Build(Name, Id));
149  }
150  OutputNumbered ON = (OutputNumbered)Output;
151 
152  if (!ON.Number.IsBetween(1, 64))
153  {
154  throw new Exception("PacLed64 output numbers must be in the range of 1-64. The supplied output number {0} is out of range.".Build(ON.Number));
155  }
156 
157  PacLed64Unit S = PacLed64Units[this.Id];
158  S.UpdateValue(ON);
159  }
160 
161 
162 
163  #endregion
164 
165 
166 
167 
168 
169 
170 
171 
172 
173  //~PacLed64()
174  //{
175  // Dispose(false);
176  //}
177 
178  //#region Dispose
179 
180 
181 
182  //public void Dispose()
183  //{
184 
185  // Dispose(true);
186  // GC.SuppressFinalize(this); // remove this from gc finalizer list
187  //}
188  //protected virtual void Dispose(bool disposing)
189  //{
190  // // Check to see if Dispose has already been called.
191  // if (!this.disposed)
192  // {
193  // // If disposing equals true, dispose all managed
194  // // and unmanaged resources.
195  // if (disposing)
196  // {
197  // // Dispose managed resources.
198 
199  // }
200 
201  // // Call the appropriate methods to clean up
202  // // unmanaged resources here.
203  // // If disposing is false,
204  // // only the following code is executed.
205 
206  // TerminatePacLed64();
207 
208 
209  // // Note disposing has been done.
210  // disposed = true;
211 
212  // }
213  //}
214  //private bool disposed = false;
215 
216  //#endregion
217 
218 
219 
220  #region Constructor
221 
222 
226  static PacLed64()
227  {
228 
229  PacLed64Units = new Dictionary<int, PacLed64Unit>();
230  for (int i = 1; i <= 4; i++)
231  {
232  PacLed64Units.Add(i, new PacLed64Unit(i));
233 
234  }
235 
236  }
237 
241  public PacLed64()
242  {
243 
244  Outputs = new OutputList();
245 
246  }
247 
248 
249 
254  public PacLed64(int Id)
255  : this()
256  {
257  this.Id = Id;
258  }
259 
260  #endregion
261 
262 
263 
264 
265 
266 
267  #region Internal class for PacLed64 output states and update methods
268 
269  private static Dictionary<int, PacLed64Unit> PacLed64Units = new Dictionary<int, PacLed64Unit>();
270 
271  private class PacLed64Unit
272  {
273  private Pinball Pinball;
274 
275  private TimeSpanStatisticsItem UpdateTimeStatistics;
276  private TimeSpanStatisticsItem PWMUpdateTimeStatistics;
277  private TimeSpanStatisticsItem OnOffUpdateTimeStatistics;
278 
279  private const int MaxUpdateFailCount = 5;
280 
281 
282  public int Id { get; private set; }
283 
284  private int Index { get; set; }
285 
286  private PacDriveSingleton PDSingleton;
287 
288  private byte[] NewValue = new byte[64];
289  private byte[] CurrentValue = new byte[64];
290 
291 
292  private byte[] LastValueSent = new byte[64];
293  private bool[] LastStateSent = new bool[64];
294 
295  public bool UpdateRequired = true;
296 
297  public object PacLed64UpdateLocker = new object();
298  public object ValueChangeLocker = new object();
299 
300  public Thread PacLed64Updater;
301  public bool KeepPacLed64UpdaterAlive = false;
302  public object PacLed64UpdaterThreadLocker = new object();
303 
304 
305  public void Init(Cabinet Cabinet)
306  {
307  this.Pinball = Cabinet.Pinball;
308  if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} update calls".Build(Id)))
309  {
310  UpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} update calls".Build(Id), GroupName = "OutputControllers - PacLed64" };
311  Pinball.TimeSpanStatistics.Add(UpdateTimeStatistics);
312  }
313  else
314  {
315  UpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} update calls".Build(Id)];
316  }
317  if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} PWM updates".Build(Id)))
318  {
319  PWMUpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} PWM updates".Build(Id), GroupName = "OutputControllers - PacLed64" };
320  Pinball.TimeSpanStatistics.Add(PWMUpdateTimeStatistics);
321  }
322  else
323  {
324  PWMUpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} PWM updates".Build(Id)];
325  }
326  if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} OnOff updates".Build(Id)))
327  {
328  OnOffUpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} OnOff updates".Build(Id), GroupName = "OutputControllers - PacLed64" };
329  Pinball.TimeSpanStatistics.Add(OnOffUpdateTimeStatistics);
330  }
331  else
332  {
333  OnOffUpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} OnOff updates".Build(Id)];
334  }
335  StartPacLed64UpdaterThread();
336  }
337 
338  public void Finish()
339  {
340 
341  TerminatePacLed64UpdaterThread();
342  ShutdownLighting();
343  this.Pinball = null;
344  UpdateTimeStatistics = null;
345  PWMUpdateTimeStatistics = null;
346  }
347 
348  public void UpdateValue(OutputNumbered OutputNumbered)
349  {
350  //Skip update on output numbers which are out of range
351  if (!OutputNumbered.Number.IsBetween(1, 64)) return;
352 
353  int ZeroBasedOutputNumber = OutputNumbered.Number - 1;
354  lock (ValueChangeLocker)
355  {
356  if (NewValue[ZeroBasedOutputNumber] != OutputNumbered.Value)
357  {
358  NewValue[ZeroBasedOutputNumber] = OutputNumbered.Value;
359  UpdateRequired = true;
360  }
361  }
362 
363  }
364  private void CopyNewToCurrent()
365  {
366  lock (ValueChangeLocker)
367  {
368  Array.Copy(NewValue, CurrentValue, NewValue.Length);
369 
370  }
371  }
372 
373  public bool IsUpdaterThreadAlive
374  {
375  get
376  {
377  if (PacLed64Updater != null)
378  {
379  return PacLed64Updater.IsAlive;
380  }
381  return false;
382  }
383  }
384 
385  public void StartPacLed64UpdaterThread()
386  {
387  lock (PacLed64UpdaterThreadLocker)
388  {
389  if (!IsUpdaterThreadAlive)
390  {
391  KeepPacLed64UpdaterAlive = true;
392  PacLed64Updater = new Thread(PacLed64UpdaterDoIt);
393  PacLed64Updater.Name = "PacLed64 {0:0} updater thread".Build(Id);
394  PacLed64Updater.Start();
395  }
396  }
397  }
398  public void TerminatePacLed64UpdaterThread()
399  {
400  lock (PacLed64UpdaterThreadLocker)
401  {
402  if (PacLed64Updater != null)
403  {
404  try
405  {
406  KeepPacLed64UpdaterAlive = false;
407  lock (PacLed64Updater)
408  {
409  Monitor.Pulse(PacLed64Updater);
410  }
411  if (!PacLed64Updater.Join(1000))
412  {
413  PacLed64Updater.Abort();
414  }
415 
416  }
417  catch (Exception E)
418  {
419  Log.Exception("A error occurd during termination of {0}.".Build(PacLed64Updater.Name), E);
420  throw new Exception("A error occurd during termination of {0}.".Build(PacLed64Updater.Name), E);
421  }
422  PacLed64Updater = null;
423  }
424  }
425  }
426 
427  bool TriggerUpdate = false;
428  public void TriggerPacLed64UpdaterThread()
429  {
430  TriggerUpdate = true;
431  lock (PacLed64UpdaterThreadLocker)
432  {
433  Monitor.Pulse(PacLed64UpdaterThreadLocker);
434  }
435  }
436 
437 
438  //TODO: Check if thread should really terminate on failed updates
439  private void PacLed64UpdaterDoIt()
440  {
441  //Pinball.ThreadInfoList.HeartBeat("PacLed64 {0:0}".Build(Id));
442 
443 
444  int FailCnt = 0;
445  while (KeepPacLed64UpdaterAlive)
446  {
447  try
448  {
449  if (IsPresent)
450  {
451  UpdateTimeStatistics.MeasurementStart();
452  SendPacLed64Update();
453  UpdateTimeStatistics.MeasurementStop();
454  }
455  FailCnt = 0;
456  }
457  catch (Exception E)
458  {
459  Log.Exception("A error occured when updating PacLed64 {0}".Build(Id), E);
460  //Pinball.ThreadInfoList.RecordException(E);
461  FailCnt++;
462 
463  if (FailCnt > MaxUpdateFailCount)
464  {
465  Log.Exception("More than {0} consecutive updates failed for PacLed64 {1}. Updater thread will terminate.".Build(MaxUpdateFailCount, Id));
466  KeepPacLed64UpdaterAlive = false;
467  }
468  }
469  //Pinball.ThreadInfoList.HeartBeat();
470  if (KeepPacLed64UpdaterAlive)
471  {
472  lock (PacLed64UpdaterThreadLocker)
473  {
474  while (!TriggerUpdate && KeepPacLed64UpdaterAlive)
475  {
476  Monitor.Wait(PacLed64UpdaterThreadLocker, 50); // Lock is released while we’re waiting
477  //Pinball.ThreadInfoList.HeartBeat();
478  }
479 
480  }
481 
482  }
483  TriggerUpdate = false;
484  }
485  //Pinball.ThreadInfoList.ThreadTerminates();
486  }
487 
488 
489 
490  private bool ForceFullUpdate = true;
491  private void SendPacLed64Update()
492  {
493  if (IsPresent)
494  {
495 
496  lock (PacLed64UpdateLocker)
497  {
498  lock (ValueChangeLocker)
499  {
500  if (!UpdateRequired && !ForceFullUpdate) return;
501 
502  CopyNewToCurrent();
503 
504  UpdateRequired = false;
505  }
506 
507  byte IntensityUpdatesRequired = 0;
508  byte StateUpdatesRequired = 0;
509  if (!ForceFullUpdate)
510  {
511 
512  for (int g = 0; g < 8; g++)
513  {
514 
515  bool StateUpdateRequired = false;
516  for (int p = 0; p < 8; p++)
517  {
518  int o = g << 3 | p;
519  if (CurrentValue[o] > 0)
520  {
521  if (CurrentValue[o] != LastValueSent[o])
522  {
523  IntensityUpdatesRequired++;
524  }
525  else if (!LastStateSent[o])
526  {
527  StateUpdateRequired = true;
528  }
529  }
530  else if (LastStateSent[o])
531  {
532  StateUpdateRequired = true;
533  }
534 
535  }
536  if (StateUpdateRequired) StateUpdatesRequired++;
537  StateUpdateRequired = false;
538  }
539  }
540  if ( ForceFullUpdate || (IntensityUpdatesRequired + StateUpdatesRequired) > 30)
541  {
542  //more than 30 update calls required. Will send intensity updates for all outputs.
543  PDSingleton.PacLed64SetLEDIntensities(Index, CurrentValue);
544  Array.Copy(CurrentValue, LastValueSent, CurrentValue.Length);
545  for (int i = 0; i < 64; i++)
546  {
547  LastStateSent[i] = (LastValueSent[i] > 0);
548  }
549  }
550  else
551  {
552  //Will send separate intensity and state updates.
553  for (int g = 0; g < 8; g++)
554  {
555  int Mask = 0;
556  bool StateUpdateRequired = false;
557  for (int p = 0; p < 8; p++)
558  {
559  int o = g << 3 | p;
560  if (CurrentValue[o] > 0)
561  {
562  if (CurrentValue[o] != LastValueSent[o])
563  {
564  PDSingleton.PacLed64SetLEDIntensity(Index, o, CurrentValue[o]);
565  LastStateSent[o] = true;
566  LastValueSent[o] = CurrentValue[o];
567  }
568  else if (!LastStateSent[o])
569  {
570  Mask |= (1 << p);
571  StateUpdateRequired = true;
572  LastStateSent[o] = true;
573  }
574  }
575  else if (LastStateSent[o])
576  {
577  StateUpdateRequired = true;
578  LastStateSent[o] = false;
579  LastValueSent[o] = 0;
580  }
581 
582  }
583  if (StateUpdateRequired)
584  {
585  PDSingleton.PacLed64SetLEDStates(Index, g + 1, (byte)Mask);
586  StateUpdateRequired = false;
587  }
588 
589  }
590  }
591 
592 
593  }
594  ForceFullUpdate = false;
595  }
596  else
597  {
598  ForceFullUpdate=true;
599  }
600  }
601 
602 
603  public void ShutdownLighting()
604  {
605  PDSingleton.PacLed64SetLEDStates(0, 0, 0);
606  LastStateSent.Fill(false);
607  }
608 
609 
610 
611 
612  private bool IsPresent
613  {
614  get
615  {
616  if (!Id.IsBetween(1, 4)) return false;
617  return Index >= 0;
618  }
619  }
620 
621 
622  void Instance_OnPacRemoved(int Index)
623  {
624  this.Index = PDSingleton.PacLed64GetIndexForDeviceId(Id);
625  }
626 
627  void Instance_OnPacAttached(int Index)
628  {
629  this.Index = PDSingleton.PacLed64GetIndexForDeviceId(Id);
630  InitUnit();
631  TriggerPacLed64UpdaterThread();
632  }
633 
634  private void InitUnit()
635  {
636  if (Index >= 0)
637  {
638  LastValueSent.Fill((byte)0);
639 
640  PDSingleton.PacLed64SetLEDIntensities(Index, LastValueSent);
641  LastStateSent.Fill(false);
642  LastValueSent.Fill((byte)0);
643  }
644  }
645 
646 
647  public PacLed64Unit(int Id)
648  {
649  this.Id = Id;
650 
651  PDSingleton = PacDriveSingleton.Instance;
652  PDSingleton.OnPacAttached += new PacDriveSingleton.PacAttachedDelegate(Instance_OnPacAttached);
653  PDSingleton.OnPacRemoved += new PacDriveSingleton.PacRemovedDelegate(Instance_OnPacRemoved);
654 
655  this.Index = PacDriveSingleton.Instance.PacLed64GetIndexForDeviceId(Id);
656 
657  NewValue.Fill((byte)0);
658 
659  InitUnit();
660 
661 
662  }
663 
664 
665  }
666 
667 
668 
669  #endregion
670 
671 
672 
673 
674  }
675 }