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
PacDrive.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 
27  #region IOutputcontroller implementation
28 
29 
30 
31  public override void Update()
32  {
33  PacDriveInstance.TriggerPacDriveUpdaterThread();
34  }
35 
36 
43  public override void Init(Cabinet Cabinet)
44  {
45  AddOutputs();
46  PacDriveInstance.Init(Cabinet);
47  Log.Write("PacDrive initialized and updater thread started.");
48 
49  }
50 
55  public override void Finish()
56  {
57  PacDriveInstance.Finish();
58  PacDriveInstance.ShutdownLighting();
59  Log.Write("PacDrive finished and updater thread stopped.");
60 
61  }
62  #endregion
63 
64 
65 
66  #region Outputs
67 
68 
73  private void AddOutputs()
74  {
75  for (int i = 1; i <= 16; i++)
76  {
77  if (!Outputs.Any(x => ((OutputNumbered)x).Number == i))
78  {
79  Outputs.Add(new OutputNumbered() { Name = "{0}.{1:00}".Build(Name, i), Number = i });
80  }
81  }
82  }
83 
84 
85 
86 
97  protected override void OnOutputValueChanged(IOutput Output)
98  {
99 
100  if (!(Output is OutputNumbered))
101  {
102  throw new Exception("The OutputValueChanged event handler for the PacDrive uni has been called by a sender which is not a OutputNumbered.");
103  }
104  OutputNumbered ON = (OutputNumbered)Output;
105 
106  if (!ON.Number.IsBetween(1, 64))
107  {
108  throw new Exception("PacDrive output numbers must be in the range of 1-16. The supplied output number {0} is out of range.".Build(ON.Number));
109  }
110 
111  PacDriveInstance.UpdateValue(ON);
112  }
113 
114 
115 
116  #endregion
117 
118 
119 
120 
121 
122 
123 
124 
125 
126  //~PacDrive()
127  //{
128  // Dispose(false);
129  //}
130 
131  //#region Dispose
132 
133 
134 
135  //public void Dispose()
136  //{
137 
138  // Dispose(true);
139  // GC.SuppressFinalize(this); // remove this from gc finalizer list
140  //}
141  //protected virtual void Dispose(bool disposing)
142  //{
143  // // Check to see if Dispose has already been called.
144  // if (!this.disposed)
145  // {
146  // // If disposing equals true, dispose all managed
147  // // and unmanaged resources.
148  // if (disposing)
149  // {
150  // // Dispose managed resources.
151 
152  // }
153 
154  // // Call the appropriate methods to clean up
155  // // unmanaged resources here.
156  // // If disposing is false,
157  // // only the following code is executed.
158 
159  // TerminatePacDrive();
160 
161 
162  // // Note disposing has been done.
163  // disposed = true;
164 
165  // }
166  //}
167  //private bool disposed = false;
168 
169  //#endregion
170 
171 
172 
173  #region Constructor
174 
175 
179  static PacDrive()
180  {
181  PacDriveInstance = new PacDriveUnit();
182  }
183 
187  public PacDrive()
188  {
189  Outputs = new OutputList();
190  Name = "PacDrive";
191  }
192 
193 
194 
195 
196  #endregion
197 
198 
199 
200 
201 
202 
203  #region Internal class for PacDrive output states and update methods
204 
205  private static PacDriveUnit PacDriveInstance=null;
206 
207  private class PacDriveUnit
208  {
209  private Pinball Pinball;
210 
211  private TimeSpanStatisticsItem UpdateTimeStatistics;
212 
213  private const int MaxUpdateFailCount = 5;
214 
215  private PacDriveSingleton PDSingleton;
216 
217  private ushort NewValue;
218  private ushort CurrentValue;
219  private bool UpdateRequired = true;
220 
221  private int Index = -1;
222 
223  public object PacDriveUpdateLocker = new object();
224  public object ValueChangeLocker = new object();
225 
226  public Thread PacDriveUpdater;
227  public bool KeepPacDriveUpdaterAlive = false;
228  public object PacDriveUpdaterThreadLocker = new object();
229 
230 
231  public void Init(Cabinet Cabinet)
232  {
233  this.Pinball = Cabinet.Pinball;
234  if (!Pinball.TimeSpanStatistics.Contains("PacDrive update calls"))
235  {
236  UpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacDrive update calls", GroupName = "OutputControllers - PacDrive" };
237  Pinball.TimeSpanStatistics.Add(UpdateTimeStatistics);
238  }
239  else
240  {
241  UpdateTimeStatistics = Pinball.TimeSpanStatistics["PacDrive update calls"];
242  }
243 
244  StartPacDriveUpdaterThread();
245  }
246 
247  public void Finish()
248  {
249 
250  TerminatePacDriveUpdaterThread();
251  ShutdownLighting();
252  this.Pinball = null;
253  UpdateTimeStatistics = null;
254  }
255 
256  public void UpdateValue(OutputNumbered OutputNumbered)
257  {
258  //Skip update on output numbers which are out of range
259  if (!OutputNumbered.Number.IsBetween(1, 16)) return;
260 
261  int ZeroBasedOutputNumber = OutputNumbered.Number - 1;
262  ushort Mask = (ushort)(1 << ZeroBasedOutputNumber);
263  lock (ValueChangeLocker)
264  {
265  if (OutputNumbered.Value != 0)
266  {
267  NewValue |= Mask;
268  }
269  else
270  {
271  NewValue &= (ushort)~Mask;
272  }
273  UpdateRequired = true;
274  }
275 
276  }
277  private void CopyNewToCurrent()
278  {
279  lock (ValueChangeLocker)
280  {
281  CurrentValue = NewValue;
282 
283  }
284  }
285 
286  public bool IsUpdaterThreadAlive
287  {
288  get
289  {
290  if (PacDriveUpdater != null)
291  {
292  return PacDriveUpdater.IsAlive;
293  }
294  return false;
295  }
296  }
297 
298  public void StartPacDriveUpdaterThread()
299  {
300  lock (PacDriveUpdaterThreadLocker)
301  {
302  if (!IsUpdaterThreadAlive)
303  {
304  KeepPacDriveUpdaterAlive = true;
305  PacDriveUpdater = new Thread(PacDriveUpdaterDoIt);
306  PacDriveUpdater.Name = "PacDrive updater thread";
307  PacDriveUpdater.Start();
308  }
309  }
310  }
311  public void TerminatePacDriveUpdaterThread()
312  {
313  lock (PacDriveUpdaterThreadLocker)
314  {
315  if (PacDriveUpdater != null)
316  {
317  try
318  {
319  KeepPacDriveUpdaterAlive = false;
320  lock (PacDriveUpdater)
321  {
322  Monitor.Pulse(PacDriveUpdater);
323  }
324  if (!PacDriveUpdater.Join(1000))
325  {
326  PacDriveUpdater.Abort();
327  }
328 
329  }
330  catch (Exception E)
331  {
332  Log.Exception("A error occurd during termination of {0}.".Build(PacDriveUpdater.Name), E);
333  throw new Exception("A error occurd during termination of {0}.".Build(PacDriveUpdater.Name), E);
334  }
335  PacDriveUpdater = null;
336  }
337  }
338  }
339 
340  bool TriggerUpdate = false;
341  public void TriggerPacDriveUpdaterThread()
342  {
343  if (!UpdateRequired) return;
344  TriggerUpdate = true;
345  UpdateRequired = false;
346  lock (PacDriveUpdaterThreadLocker)
347  {
348  Monitor.Pulse(PacDriveUpdaterThreadLocker);
349  }
350  }
351 
352 
353  //TODO: Check if thread should really terminate on failed updates
354  private void PacDriveUpdaterDoIt()
355  {
356  //Pinball.ThreadInfoList.HeartBeat("PacDrive");
357 
358 
359  int FailCnt = 0;
360  while (KeepPacDriveUpdaterAlive)
361  {
362  try
363  {
364  if (IsPresent)
365  {
366  UpdateTimeStatistics.MeasurementStart();
367  SendPacDriveUpdate();
368  UpdateTimeStatistics.MeasurementStop();
369  }
370  FailCnt = 0;
371  }
372  catch (Exception E)
373  {
374  Log.Exception("A error occured when updating PacDrive", E);
375  //Pinball.ThreadInfoList.RecordException(E);
376  FailCnt++;
377 
378  if (FailCnt > MaxUpdateFailCount)
379  {
380  Log.Exception("More than {0} consecutive updates failed for PacDrive. Updater thread will terminate.".Build(MaxUpdateFailCount));
381  KeepPacDriveUpdaterAlive = false;
382  }
383  }
384  //Pinball.ThreadInfoList.HeartBeat();
385  if (KeepPacDriveUpdaterAlive)
386  {
387  lock (PacDriveUpdaterThreadLocker)
388  {
389  while (!TriggerUpdate && KeepPacDriveUpdaterAlive)
390  {
391  Monitor.Wait(PacDriveUpdaterThreadLocker, 50); // Lock is released while we’re waiting
392  //Pinball.ThreadInfoList.HeartBeat();
393  }
394 
395  }
396 
397  }
398  TriggerUpdate = false;
399  }
400 
401  ShutdownLighting();
402 
403  //Pinball.ThreadInfoList.ThreadTerminates();
404  }
405 
406 
407 
408  private bool ForceFullUpdate = true;
409  private void SendPacDriveUpdate()
410  {
411  if (IsPresent)
412  {
413 
414  lock (PacDriveUpdateLocker)
415  {
416  lock (ValueChangeLocker)
417  {
418  if (NewValue == CurrentValue && !ForceFullUpdate) return;
419 
420  CopyNewToCurrent();
421 
422  }
423 
424 
425  PDSingleton.PacDriveUHIDSetLEDStates(Index, CurrentValue);
426 
427  }
428  ForceFullUpdate = false;
429  }
430  else
431  {
432  ForceFullUpdate = true;
433  }
434  }
435 
436 
437  public void ShutdownLighting()
438  {
439  lock (PacDriveUpdateLocker)
440  {
441  lock (ValueChangeLocker)
442  {
443  CurrentValue = 0;
444  NewValue = 0;
445 
446  PDSingleton.PacDriveUHIDSetLEDStates(Index, 0);
447  }
448  }
449  }
450 
451 
452 
453  private bool IsPresent
454  {
455  get
456  {
457  return Index >= 0;
458  }
459  }
460 
461 
462  void Instance_OnPacRemoved(int Index)
463  {
464  this.Index = PDSingleton.PacDriveGetIndex();
465  }
466 
467  void Instance_OnPacAttached(int Index)
468  {
469  this.Index = PDSingleton.PacDriveGetIndex();
470  InitUnit();
471  TriggerPacDriveUpdaterThread();
472  }
473 
474  private void InitUnit()
475  {
476  lock (ValueChangeLocker)
477  {
478  NewValue = 0;
479  CurrentValue = 65535;
480  };
481  SendPacDriveUpdate();
482  }
483 
484 
485  public PacDriveUnit()
486  {
487  PDSingleton = PacDriveSingleton.Instance;
488  PDSingleton.OnPacAttached += new PacDriveSingleton.PacAttachedDelegate(Instance_OnPacAttached);
489  PDSingleton.OnPacRemoved += new PacDriveSingleton.PacRemovedDelegate(Instance_OnPacRemoved);
490  this.Index = PacDriveSingleton.Instance.PacDriveGetIndex();
491  InitUnit();
492 
493 
494  }
495 
496 
497  }
498 
499 
500 
501  #endregion
502 
503 
504 
505 
506  }
507 }