WIP
DirectOutput framework for virtual pinball cabinets WIP
Go to:
Overview 
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;
9 
10 
11 namespace DirectOutput.Cab.Out.Pac
12 {
25  {
26 
27  #region IOutputcontroller implementation
28  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 => x.Number == i))
78  {
79  Outputs.Add(new Output() { 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 IOutput))
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  IOutput ON = 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 
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 
253 
254  }
255 
256  public void UpdateValue(IOutput Output)
257  {
258  //Skip update on output numbers which are out of range
259  if (!Output.Number.IsBetween(1, 16)) return;
260 
261  int ZeroBasedOutputNumber = Output.Number - 1;
262  ushort Mask = (ushort)(1 << ZeroBasedOutputNumber);
263  lock (ValueChangeLocker)
264  {
265  if (Output.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  TriggerPacDriveUpdaterThread();
321  if (!PacDriveUpdater.Join(1000))
322  {
323  PacDriveUpdater.Abort();
324  }
325 
326  }
327  catch (Exception E)
328  {
329  Log.Exception("A error occurd during termination of {0}.".Build(PacDriveUpdater.Name), E);
330  throw new Exception("A error occurd during termination of {0}.".Build(PacDriveUpdater.Name), E);
331  }
332  PacDriveUpdater = null;
333  }
334  }
335  }
336 
337  bool TriggerUpdate = false;
338  public void TriggerPacDriveUpdaterThread()
339  {
340  if (!UpdateRequired) return;
341  TriggerUpdate = true;
342  UpdateRequired = false;
343  lock (PacDriveUpdaterThreadLocker)
344  {
345  Monitor.Pulse(PacDriveUpdaterThreadLocker);
346  }
347  }
348 
349 
350  //TODO: Check if thread should really terminate on failed updates
351  private void PacDriveUpdaterDoIt()
352  {
353  //Pinball.ThreadInfoList.HeartBeat("PacDrive");
354 
355 
356  int FailCnt = 0;
357  while (KeepPacDriveUpdaterAlive)
358  {
359  try
360  {
361  if (IsPresent)
362  {
363 
364  SendPacDriveUpdate();
365 
366  }
367  FailCnt = 0;
368  }
369  catch (Exception E)
370  {
371  Log.Exception("A error occured when updating PacDrive", E);
372  //Pinball.ThreadInfoList.RecordException(E);
373  FailCnt++;
374 
375  if (FailCnt > MaxUpdateFailCount)
376  {
377  Log.Exception("More than {0} consecutive updates failed for PacDrive. Updater thread will terminate.".Build(MaxUpdateFailCount));
378  KeepPacDriveUpdaterAlive = false;
379  }
380  }
381  //Pinball.ThreadInfoList.HeartBeat();
382  if (KeepPacDriveUpdaterAlive)
383  {
384  lock (PacDriveUpdaterThreadLocker)
385  {
386  while (!TriggerUpdate && KeepPacDriveUpdaterAlive)
387  {
388  Monitor.Wait(PacDriveUpdaterThreadLocker, 50); // Lock is released while we’re waiting
389  //Pinball.ThreadInfoList.HeartBeat();
390  }
391 
392  }
393 
394  }
395  TriggerUpdate = false;
396  }
397 
398  ShutdownLighting();
399 
400  //Pinball.ThreadInfoList.ThreadTerminates();
401  }
402 
403 
404 
405  private bool ForceFullUpdate = true;
406  private void SendPacDriveUpdate()
407  {
408  if (IsPresent)
409  {
410 
411  lock (PacDriveUpdateLocker)
412  {
413  lock (ValueChangeLocker)
414  {
415  if (NewValue == CurrentValue && !ForceFullUpdate) return;
416 
417  CopyNewToCurrent();
418 
419  }
420 
421 
422  PDSingleton.PacDriveUHIDSetLEDStates(Index, CurrentValue);
423 
424  }
425  ForceFullUpdate = false;
426  }
427  else
428  {
429  ForceFullUpdate = true;
430  }
431  }
432 
433 
434  public void ShutdownLighting()
435  {
436  lock (PacDriveUpdateLocker)
437  {
438  lock (ValueChangeLocker)
439  {
440  CurrentValue = 0;
441  NewValue = 0;
442 
443  PDSingleton.PacDriveUHIDSetLEDStates(Index, 0);
444  }
445  }
446  }
447 
448 
449 
450  private bool IsPresent
451  {
452  get
453  {
454  return Index >= 0;
455  }
456  }
457 
458 
459  void Instance_OnPacRemoved(int Index)
460  {
461  this.Index = PDSingleton.PacDriveGetIndex();
462  }
463 
464  void Instance_OnPacAttached(int Index)
465  {
466  this.Index = PDSingleton.PacDriveGetIndex();
467  InitUnit();
468  TriggerPacDriveUpdaterThread();
469  }
470 
471  private void InitUnit()
472  {
473  lock (ValueChangeLocker)
474  {
475  NewValue = 0;
476  CurrentValue = 65535;
477  };
478  SendPacDriveUpdate();
479  }
480 
481 
482  public PacDriveUnit()
483  {
484  PDSingleton = PacDriveSingleton.Instance;
485  PDSingleton.OnPacAttached += new PacDriveSingleton.PacAttachedDelegate(Instance_OnPacAttached);
486  PDSingleton.OnPacRemoved += new PacDriveSingleton.PacRemovedDelegate(Instance_OnPacRemoved);
487  this.Index = PacDriveSingleton.Instance.PacDriveGetIndex();
488  InitUnit();
489 
490 
491  }
492 
493 
494  }
495 
496 
497 
498  #endregion
499 
500 
501 
502 
503  }
504 }
The Cabinet object describes the parts of a pinball cabinet (toys, outputcontrollers, outputs and more).
Definition: Cabinet.cs:17
int Number
Gets or sets the number of the Output object.
Definition: IOutput.cs:27
Singleton version of the PacDrive class found in the PacDrive SDK.
override void Finish()
Finishes the PacDrive object. Finish does also terminate the workerthread.
Definition: PacDrive.cs:55
List of IOutput objects
Definition: OutputList.cs:13
static void Write(string Message)
Writes the specified message to the logfile.
Definition: Log.cs:99
A simple logger used to record important events and exceptions.
Definition: Log.cs:14
PacDrive()
Initializes a new instance of the PacDriveInstance class.
Definition: PacDrive.cs:187
override void Init(Cabinet Cabinet)
Initializes the PacDrive object. This method does also start the workerthread which does the actual ...
Definition: PacDrive.cs:43
Common interface for outputs of any output controller. The Output class implements this interface and...
Definition: IOutput.cs:10
Abstract OutputController base class to be used for IOutputController implementations. Implements IOutputController.
The PacDrive is a simple output controller with 16 digital/on off outputs.
Definition: PacDrive.cs:24
Common interface for all outputcontrollers. Only classes implementing this interface can be used as o...
bool PacDriveUHIDSetLEDStates(int Index, ushort Data)
int PacDriveGetIndex()
Gets the Ids of the first PacDrive controller which is connected to the system.
override void OnOutputValueChanged(IOutput Output)
This method is called whenever the value of a output in the Outputs property changes its value...
Definition: PacDrive.cs:97
The namespace DirectOutput.General contains classes for general use.
Basic IOutput implementation.
Definition: Output.cs:14