WIP
DirectOutput framework for virtual pinball cabinets WIP
Go to:
Overview 
OutputControllerCompleteBase.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.Xml.Serialization;
7 using System.Threading;
8 
9 namespace DirectOutput.Cab.Out
10 {
16  {
17 
18  byte[] OutputValues = new byte[0];
19 
20 
21 
22 
23 
27  protected void SetupOutputs()
28  {
29  int NumberOfOutputs = GetNumberOfConfiguredOutputs().Limit(0, int.MaxValue);
30  if (Outputs == null)
31  {
32  Outputs = new OutputList();
33  }
34 
35  Outputs.Where(O => O.Number > NumberOfOutputs).ToList().ForEach(ODEL => Outputs.Remove(ODEL));
36 
37  for (int i = 1; i <= NumberOfOutputs; i++)
38  {
39  if (!Outputs.Any(O => O.Number == i))
40  {
41  Outputs.Add(new Output() { Name = "{0}.{1}".Build((Name != null ? Name : ""), i), Number = i, Value = 0 });
42  }
43  }
44 
45 
46  OutputValues = Outputs.OrderBy(O => O.Number).Select(O => O.Value).ToArray();
47  }
48 
49  private void RenameOutputs()
50  {
51  if (Outputs != null)
52  {
53  foreach (IOutput O in Outputs)
54  {
55  O.Name = "{0}.{1}".Build((Name != null ? Name : ""), O.Name);
56  }
57  }
58  }
59 
60  protected override void AfterNameChange(string OldName, string NewName)
61  {
62  RenameOutputs();
63  base.AfterNameChange(OldName, NewName);
64  }
65 
66 
67  private OutputList _Outputs = null;
72  [XmlIgnore]
73  public OutputList Outputs
74  {
75  get { return _Outputs; }
76  set
77  {
78  if (_Outputs != null)
79  {
80  _Outputs.OutputValueChanged -= new OutputList.OutputValueChangedEventHandler(Outputs_OutputValueChanged);
81  }
82 
83  _Outputs = value;
84 
85  if (_Outputs != null)
86  {
87  _Outputs.OutputValueChanged += new OutputList.OutputValueChangedEventHandler(Outputs_OutputValueChanged);
88 
89  }
90 
91  }
92  }
93 
94  private void Outputs_OutputValueChanged(object sender, OutputEventArgs e)
95  {
96  OnOutputValueChanged(e.Output);
97  }
98 
104  private void OnOutputValueChanged(IOutput Output)
105  {
106  if (Output.Number > 0 && Output.Number <= OutputValues.Length)
107  {
108  lock (ValueChangeLocker)
109  {
110  if (OutputValues[Output.Number - 1] != Output.Value)
111  {
112  OutputValues[Output.Number - 1] = Output.Value;
113  UpdateRequired = true;
114  }
115 
116  }
117  }
118  }
119 
120 
121 
122  #region ISupportsSetValues Member
123 
129  public void SetValues(int FirstOutput, byte[] Values)
130  {
131  if (FirstOutput >= OutputValues.Length) return;
132  if (FirstOutput < 0) return;
133  int CopyLength = (OutputValues.Length - FirstOutput).Limit(0, Values.Length);
134  if (CopyLength < 1) return;
135 
136  lock (ValueChangeLocker)
137  {
138  Buffer.BlockCopy(Values, 0, OutputValues, FirstOutput, CopyLength);
139  UpdateRequired = true;
140  }
141  }
142 
143  #endregion
144 
145 
146 
151  public virtual void Init(Cabinet Cabinet)
152  {
153  bool V = false;
154  try
155  {
156  V = VerifySettings();
157  }
158  catch (Exception E)
159  {
160  string msg = "A exception occured when verifying the settings for {0} {1}: {2}. Cant initialize.".Build(this.GetType().Name, Name, E.Message);
161  Log.Exception(msg, E);
162  throw new Exception(msg, E);
163  }
164  if (V)
165  {
166  SetupOutputs();
167  InitUpdaterThread();
168  Log.Write("{0} {1} intialized and updater thread started.".Build(this.GetType().Name, Name));
169  }
170  else
171  {
172  Log.Warning("Settings for {0} {1} are not correct. Cant initialize.".Build(this.GetType().Name, Name));
173  }
174  }
175 
176 
180  public virtual void Finish()
181  {
182  FinishUpdaterThread();
183  Log.Write("{0} {1} finished and updater thread stopped.".Build(this.GetType().Name, Name));
184  }
185 
189  public void Update()
190  {
191  if (UpdateRequired)
192  {
193  UpdaterThreadSignal();
194  }
195  }
196 
197 
204  protected abstract int GetNumberOfConfiguredOutputs();
205 
210  protected abstract bool VerifySettings();
211 
217  protected abstract void UpdateOutputs(byte[] OutputValues);
218 
219 
220 
225  protected abstract void ConnectToController();
226 
230 
232  protected abstract void DisconnectFromController();
233 
234 
235  #region UpdaterThread
236  private void InitUpdaterThread()
241  {
242 
243  if (!UpdaterThreadIsActive)
244  {
245  KeepUpdaterThreadAlive = true;
246  try
247  {
248  UpdaterThread = new Thread(UpdaterThreadDoIt);
249  UpdaterThread.Name = "{0} {1} updater thread ".Build(this.GetType().Name, Name);
250  UpdaterThread.Start();
251  }
252  catch (Exception E)
253  {
254  Log.Exception("{0} {1} updater thread could not start.".Build(this.GetType().Name, Name), E);
255  throw new Exception("{0} {1} updater thread could not start.".Build(this.GetType().Name, Name), E);
256  }
257  }
258  }
259 
263 
264  private void FinishUpdaterThread()
265  {
266  if (UpdaterThread != null)
267  {
268  try
269  {
270  KeepUpdaterThreadAlive = false;
271  UpdaterThreadSignal();
272  if (!UpdaterThread.Join(1000))
273  {
274  UpdaterThread.Abort();
275  Log.Warning("{0} did not quit. Forcing abort.".Build(UpdaterThread.Name));
276  }
277  UpdaterThread = null;
278  }
279  catch (Exception E)
280  {
281  Log.Exception("A error occured during termination of the {0}: {1}.".Build(UpdaterThread.Name), E);
282  throw new Exception("A error occured during termination of the {0}: {1}.".Build(UpdaterThread.Name), E);
283  }
284  }
285  }
286 
287 
291  public bool UpdaterThreadIsActive
292  {
293  get
294  {
295  if (UpdaterThread != null)
296  {
297  if (UpdaterThread.IsAlive)
298  {
299  return true;
300  }
301  }
302  return false;
303  }
304  }
305 
309  private void UpdaterThreadSignal()
310  {
311  lock (UpdaterThreadLocker)
312  {
313  Monitor.Pulse(UpdaterThreadLocker);
314  }
315  }
316 
317 
318  private object ValueChangeLocker = new object();
319 
320  private Thread UpdaterThread { get; set; }
321  private object UpdaterThreadLocker = new object();
322  private bool KeepUpdaterThreadAlive = true;
323 
324  private bool UpdateRequired = false;
325 
326  private void UpdaterThreadDoIt()
327  {
328  Log.Write("{0} started.".Build(Thread.CurrentThread.Name));
329  try
330  {
331  try
332  {
333  ConnectToController();
334 
335  }
336  catch (Exception E)
337  {
338  Log.Exception("{0} could not connect to the controller. Thread will quit.".Build(Thread.CurrentThread.Name), E);
339 
340  try
341  {
342  DisconnectFromController();
343  return;
344  }
345  catch { }
346  }
347 
348  Log.Write("{0} has connected to {1} {2}.".Build(Thread.CurrentThread.Name, this.GetType().Name, Name));
349 
350  while (KeepUpdaterThreadAlive)
351  {
352  byte[] ValuesToSend = new byte[0];
353  lock (ValueChangeLocker)
354  {
355  ValuesToSend = (byte[])OutputValues.Clone();
356  }
357 
358 
359  bool UpdateOK = true;
360  for (int i = 0; i < 1; i++)
361  {
362 
363  try
364  {
365  UpdateOutputs(ValuesToSend);
366  }
367  catch (Exception E)
368  {
369  Log.Exception("{0} could not send update for {1} {2}: {3}. Will try again.".Build(new object[] { Thread.CurrentThread.Name, this.GetType().Name, Name, E.Message }), E);
370  UpdateOK = false;
371  }
372  if (UpdateOK) break;
373  }
374 
375  if (!UpdateOK)
376  {
377  Log.Warning("{0} tries to reconnect to {1} {2}.".Build(Thread.CurrentThread.Name, this.GetType().Name, Name));
378  try
379  {
380  DisconnectFromController();
381  }
382  catch { }
383  Thread.Sleep(100);
384  try
385  {
386  ConnectToController();
387 
388  }
389  catch (Exception E)
390  {
391  Log.Exception("{0} could not reconnect to the controller. Thread will quit.".Build(Thread.CurrentThread.Name), E);
392 
393  try
394  {
395  DisconnectFromController();
396  return;
397  }
398  catch { }
399  }
400  Log.Write("{0} has reconnected to {1} {2}.".Build(Thread.CurrentThread.Name, this.GetType().Name, Name));
401  Thread.Sleep(100);
402  try
403  {
404  UpdateOutputs(ValuesToSend);
405  }
406  catch (Exception E)
407  {
408  Log.Exception("{0} could still not send update for {1} {2}: {3}. Thread will quit.".Build(new object[] { Thread.CurrentThread.Name, this.GetType().Name, Name, E.Message }), E);
409  try
410  {
411  DisconnectFromController();
412  return;
413  }
414  catch { }
415  }
416 
417  }
418 
419 
420 
421  if (KeepUpdaterThreadAlive)
422  {
423  lock (UpdaterThreadLocker)
424  {
425  while (!UpdateRequired && KeepUpdaterThreadAlive)
426  {
427  Monitor.Wait(UpdaterThreadLocker, 50); // Lock is released while we’re waiting
428  }
429  }
430 
431  }
432  UpdateRequired = false;
433  }
434 
435  try
436  {
437  UpdateOutputs(new byte[OutputValues.Length]);
438  }
439  catch (Exception E)
440  {
441  Log.Exception("A exception occured in {0} while trying to turn of all outputs for {1} {2}".Build(Thread.CurrentThread.Name, this.GetType().Name, Name), E);
442  }
443  try
444  {
445  DisconnectFromController();
446 
447  }
448  catch {}
449 
450  Log.Write("{0} has disconnected from {1} {2} and will terminate.".Build(Thread.CurrentThread.Name, this.GetType().Name, Name));
451  }
452  catch (Exception EU)
453  {
454  Log.Exception("A exception has occured in {0}. Thread will quit. Message: {1}".Build(Thread.CurrentThread.Name, EU.Message), EU);
455  }
456 
457 
458  }
459 
460  #endregion
461 
462 
463  }
464 }
IOutput Output
IOutput objec^t which has triggered the event.
The Cabinet object describes the parts of a pinball cabinet (toys, outputcontrollers, outputs and more).
Definition: Cabinet.cs:17
void Update()
Triggers the update of the physical outputs
Abstract base class for named items. Implements the name property and the necessary events...
OutputValueChangedEventHandler OutputValueChanged
Event fires if the value of any IOutput in the list has changed.
Definition: OutputList.cs:136
static void Warning(string Message)
Writes a warning message to the log.
Definition: Log.cs:134
string Name
Gets or sets the name of the item. Must fire the BeforeNameChange and AfterNameChange events when th...
Definition: INamedItem.cs:17
List of IOutput objects
Definition: OutputList.cs:13
virtual void Finish()
Finishes the output controller and stop the updater thread.
override void AfterNameChange(string OldName, string NewName)
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
This abstract class implement the full base logic for a output controller with a separate thread for ...
Common interface for outputs of any output controller. The Output class implements this interface and...
Definition: IOutput.cs:10
void SetupOutputs()
Manages to output object of the output controller. Use the GetNumberOfConfiguredOutputs() method to d...
Common interface for all outputcontrollers. Only classes implementing this interface can be used as o...
EventArgs for events of IOutput objects.
This interface defines additional methods for output controllers which allow for direct modification ...
void SetValues(int FirstOutput, byte[] Values)
Sets the values for one or several outputs of the controller.
virtual void Init(Cabinet Cabinet)
Initializes the output controller and starts the updater thread.
delegate void OutputValueChangedEventHandler(object sender, OutputEventArgs e)
Handler for the OutputValueChangedEvent.
The namespace DirectOutput.General contains classes for general use.
static void Exception(string Message, Exception E=null)
Writes a exception message to the log.
Definition: Log.cs:144
Basic IOutput implementation.
Definition: Output.cs:14