DirectOutputR1
DirectOutput framework R1 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  public 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  PacLed64Units = new Dictionary<int, PacLed64Unit>();
229  for (int i = 1; i <= 4; i++)
230  {
231  PacLed64Units.Add(i, new PacLed64Unit(i));
232  }
233  }
234 
238  public PacLed64()
239  {
240  Outputs = new OutputList();
241 
242  }
243 
244 
245 
250  public PacLed64(int Id)
251  : this()
252  {
253  this.Id = Id;
254  }
255 
256  #endregion
257 
258 
259 
260 
261 
262 
263  #region Internal class for PacLed64 output states and update methods
264 
265  private static Dictionary<int, PacLed64Unit> PacLed64Units = new Dictionary<int, PacLed64Unit>();
266 
267  private class PacLed64Unit
268  {
269  private Pinball Pinball;
270 
271  private TimeSpanStatisticsItem UpdateTimeStatistics;
272  private TimeSpanStatisticsItem PWMUpdateTimeStatistics;
273  private TimeSpanStatisticsItem OnOffUpdateTimeStatistics;
274 
275  private const int MaxUpdateFailCount = 5;
276 
277 
278  public int Id { get; private set; }
279 
280  private int Index { get; set; }
281 
282  private PacDriveSingleton PDSingleton;
283 
284  private byte[] NewValue = new byte[64];
285  private byte[] CurrentValue = new byte[64];
286 
287 
288  private byte[] LastValueSent = new byte[64];
289  private bool[] LastStateSent = new bool[64];
290 
291  public bool UpdateRequired = true;
292 
293  public object PacLed64UpdateLocker = new object();
294  public object ValueChangeLocker = new object();
295 
296  public Thread PacLed64Updater;
297  public bool KeepPacLed64UpdaterAlive = false;
298  public object PacLed64UpdaterThreadLocker = new object();
299 
300 
301  public void Init(Cabinet Cabinet)
302  {
303  this.Pinball = Cabinet.Pinball;
304  if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} update calls".Build(Id)))
305  {
306  UpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} update calls".Build(Id), GroupName = "OutputControllers - PacLed64" };
307  Pinball.TimeSpanStatistics.Add(UpdateTimeStatistics);
308  }
309  else
310  {
311  UpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} update calls".Build(Id)];
312  }
313  if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} PWM updates".Build(Id)))
314  {
315  PWMUpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} PWM updates".Build(Id), GroupName = "OutputControllers - PacLed64" };
316  Pinball.TimeSpanStatistics.Add(PWMUpdateTimeStatistics);
317  }
318  else
319  {
320  PWMUpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} PWM updates".Build(Id)];
321  }
322  if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} OnOff updates".Build(Id)))
323  {
324  OnOffUpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} OnOff updates".Build(Id), GroupName = "OutputControllers - PacLed64" };
325  Pinball.TimeSpanStatistics.Add(OnOffUpdateTimeStatistics);
326  }
327  else
328  {
329  OnOffUpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} OnOff updates".Build(Id)];
330  }
331  StartPacLed64UpdaterThread();
332  }
333 
334  public void Finish()
335  {
336 
337  TerminatePacLed64UpdaterThread();
338  ShutdownLighting();
339  this.Pinball = null;
340  UpdateTimeStatistics = null;
341  PWMUpdateTimeStatistics = null;
342  }
343 
344  public void UpdateValue(OutputNumbered OutputNumbered)
345  {
346  //Skip update on output numbers which are out of range
347  if (!OutputNumbered.Number.IsBetween(1, 64)) return;
348 
349  int ZeroBasedOutputNumber = OutputNumbered.Number - 1;
350  lock (ValueChangeLocker)
351  {
352  if (NewValue[ZeroBasedOutputNumber] != OutputNumbered.Value)
353  {
354  NewValue[ZeroBasedOutputNumber] = OutputNumbered.Value;
355  UpdateRequired = true;
356  }
357  }
358 
359  }
360  private void CopyNewToCurrent()
361  {
362  lock (ValueChangeLocker)
363  {
364  Array.Copy(NewValue, CurrentValue, NewValue.Length);
365 
366  }
367  }
368 
369  public bool IsUpdaterThreadAlive
370  {
371  get
372  {
373  if (PacLed64Updater != null)
374  {
375  return PacLed64Updater.IsAlive;
376  }
377  return false;
378  }
379  }
380 
381  public void StartPacLed64UpdaterThread()
382  {
383  lock (PacLed64UpdaterThreadLocker)
384  {
385  if (!IsUpdaterThreadAlive)
386  {
387  KeepPacLed64UpdaterAlive = true;
388  PacLed64Updater = new Thread(PacLed64UpdaterDoIt);
389  PacLed64Updater.Name = "PacLed64 {0:0} updater thread".Build(Id);
390  PacLed64Updater.Start();
391  }
392  }
393  }
394  public void TerminatePacLed64UpdaterThread()
395  {
396  lock (PacLed64UpdaterThreadLocker)
397  {
398  if (PacLed64Updater != null)
399  {
400  try
401  {
402  KeepPacLed64UpdaterAlive = false;
403  lock (PacLed64Updater)
404  {
405  Monitor.Pulse(PacLed64Updater);
406  }
407  if (!PacLed64Updater.Join(1000))
408  {
409  PacLed64Updater.Abort();
410  }
411 
412  }
413  catch (Exception E)
414  {
415  Log.Exception("A error occurd during termination of {0}.".Build(PacLed64Updater.Name), E);
416  throw new Exception("A error occurd during termination of {0}.".Build(PacLed64Updater.Name), E);
417  }
418  PacLed64Updater = null;
419  }
420  }
421  }
422 
423  bool TriggerUpdate = false;
424  public void TriggerPacLed64UpdaterThread()
425  {
426  TriggerUpdate = true;
427  lock (PacLed64UpdaterThreadLocker)
428  {
429  Monitor.Pulse(PacLed64UpdaterThreadLocker);
430  }
431  }
432 
433 
434  //TODO: Check if thread should really terminate on failed updates
435  private void PacLed64UpdaterDoIt()
436  {
437  Pinball.ThreadInfoList.HeartBeat("PacLed64 {0:0}".Build(Id));
438 
439 
440  int FailCnt = 0;
441  while (KeepPacLed64UpdaterAlive)
442  {
443  try
444  {
445  if (IsPresent)
446  {
447  UpdateTimeStatistics.MeasurementStart();
448  SendPacLed64Update();
449  UpdateTimeStatistics.MeasurementStop();
450  }
451  FailCnt = 0;
452  }
453  catch (Exception E)
454  {
455  Log.Exception("A error occured when updating PacLed64 {0}".Build(Id), E);
456  Pinball.ThreadInfoList.RecordException(E);
457  FailCnt++;
458 
459  if (FailCnt > MaxUpdateFailCount)
460  {
461  Log.Exception("More than {0} consecutive updates failed for PacLed64 {1}. Updater thread will terminate.".Build(MaxUpdateFailCount, Id));
462  KeepPacLed64UpdaterAlive = false;
463  }
464  }
465  Pinball.ThreadInfoList.HeartBeat();
466  if (KeepPacLed64UpdaterAlive)
467  {
468  lock (PacLed64UpdaterThreadLocker)
469  {
470  while (!TriggerUpdate && KeepPacLed64UpdaterAlive)
471  {
472  Monitor.Wait(PacLed64UpdaterThreadLocker, 50); // Lock is released while we’re waiting
473  Pinball.ThreadInfoList.HeartBeat();
474  }
475 
476  }
477 
478  }
479  TriggerUpdate = false;
480  }
481  Pinball.ThreadInfoList.ThreadTerminates();
482  }
483 
484 
485 
486  private bool ForceFullUpdate = true;
487  private void SendPacLed64Update()
488  {
489  if (IsPresent)
490  {
491 
492  lock (PacLed64UpdateLocker)
493  {
494  lock (ValueChangeLocker)
495  {
496  if (!UpdateRequired && !ForceFullUpdate) return;
497 
498  CopyNewToCurrent();
499 
500  UpdateRequired = false;
501  }
502 
503  byte IntensityUpdatesRequired = 0;
504  byte StateUpdatesRequired = 0;
505  if (!ForceFullUpdate)
506  {
507 
508  for (int g = 0; g < 8; g++)
509  {
510 
511  bool StateUpdateRequired = false;
512  for (int p = 0; p < 8; p++)
513  {
514  int o = g << 3 | p;
515  if (CurrentValue[o] > 0)
516  {
517  if (CurrentValue[o] != LastValueSent[o])
518  {
519  IntensityUpdatesRequired++;
520  }
521  else if (!LastStateSent[o])
522  {
523  StateUpdateRequired = true;
524  }
525  }
526  else if (LastStateSent[o])
527  {
528  StateUpdateRequired = true;
529  }
530 
531  }
532  if (StateUpdateRequired) StateUpdatesRequired++;
533  StateUpdateRequired = false;
534  }
535  }
536  if (ForceFullUpdate || (IntensityUpdatesRequired + StateUpdatesRequired) > 30)
537  {
538  //more than 30 update calls required. Will send intensity updates for all outputs.
539  PDSingleton.PacLed64SetLEDIntensities(Index, CurrentValue);
540  Array.Copy(CurrentValue, LastValueSent, CurrentValue.Length);
541  for (int i = 0; i < 64; i++)
542  {
543  LastStateSent[i] = (LastValueSent[i] > 0);
544  }
545  }
546  else
547  {
548  //Will send separate intensity and state updates.
549  for (int g = 0; g < 8; g++)
550  {
551  int Mask = 0;
552  bool StateUpdateRequired = false;
553  for (int p = 0; p < 8; p++)
554  {
555  int o = g << 3 | p;
556  if (CurrentValue[o] > 0)
557  {
558  if (CurrentValue[o] != LastValueSent[o])
559  {
560  PDSingleton.PacLed64SetLEDIntensity(Index, o, CurrentValue[o]);
561  LastStateSent[o] = true;
562  LastValueSent[o] = CurrentValue[o];
563  }
564  else if (!LastStateSent[o])
565  {
566  Mask |= (1 << p);
567  StateUpdateRequired = true;
568  LastStateSent[o] = true;
569  }
570  }
571  else if (LastStateSent[o])
572  {
573  StateUpdateRequired = true;
574  LastStateSent[o] = false;
575  }
576 
577  }
578  if (StateUpdateRequired)
579  {
580  PDSingleton.PacLed64SetLEDStates(Index, g + 1, (byte)Mask);
581  StateUpdateRequired = false;
582  }
583 
584  }
585  }
586 
587 
588  }
589  ForceFullUpdate = false;
590  }
591  else
592  {
593  ForceFullUpdate=true;
594  }
595  }
596 
597 
598  public void ShutdownLighting()
599  {
600  PDSingleton.PacLed64SetLEDStates(0, 0, 0);
601  LastStateSent.Fill(false);
602  }
603 
604 
605 
606 
607  private bool IsPresent
608  {
609  get
610  {
611  if (!Id.IsBetween(1, 4)) return false;
612  return Index >= 0;
613  }
614  }
615 
616 
617  void Instance_OnPacRemoved(int Index)
618  {
619  this.Index = PDSingleton.PacLed64GetIndexForDeviceId(Id);
620  }
621 
622  void Instance_OnPacAttached(int Index)
623  {
624  this.Index = PDSingleton.PacLed64GetIndexForDeviceId(Id);
625  InitUnit();
626  TriggerPacLed64UpdaterThread();
627  }
628 
629  private void InitUnit()
630  {
631  LastValueSent.Fill((byte)0);
632  PDSingleton.PacLed64SetLEDIntensities(Index, LastValueSent);
633  LastStateSent.Fill(false);
634  LastValueSent.Fill((byte)0);
635  }
636 
637 
638  public PacLed64Unit(int Id)
639  {
640  this.Id = Id;
641  PDSingleton = PacDriveSingleton.Instance;
642  PDSingleton.OnPacAttached += new PacDriveSingleton.PacAttachedDelegate(Instance_OnPacAttached);
643  PDSingleton.OnPacRemoved += new PacDriveSingleton.PacRemovedDelegate(Instance_OnPacRemoved);
644  this.Index = PacDriveSingleton.Instance.PacLed64GetIndexForDeviceId(Id);
645  NewValue.Fill((byte)0);
646  InitUnit();
647 
648 
649  }
650 
651 
652  }
653 
654 
655 
656  #endregion
657 
658 
659 
660 
661  }
662 }