Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
WindowsMessageLoop.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 //
4 // Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 // THE SOFTWARE.
23 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP && SILICONSTUDIO_PARADOX_GRAPHICS_API_DIRECT3D
24 using System;
25 using System.Globalization;
26 using System.Windows.Forms;
27 using System.Runtime.InteropServices;
28 
29 using SharpDX.Win32;
30 
31 namespace SiliconStudio.Paradox.Games
32 {
33 
34  /// <summary>
35  /// RenderLoop provides a rendering loop infrastructure. See remarks for usage.
36  /// </summary>
37  /// <remarks>
38  /// Use static <see cref="Run(System.Windows.Forms.Control,SharpDX.Windows.RenderLoop.RenderCallback)"/>
39  /// method to directly use a renderloop with a render callback or use your own loop:
40  /// <code>
41  /// control.Show();
42  /// using (var loop = new RenderLoop(control))
43  /// {
44  /// while (loop.NextFrame())
45  /// {
46  /// // Perform draw operations here.
47  /// }
48  /// }
49  /// </code>
50  /// Note that the main control can be changed at anytime inside the loop.
51  /// </remarks>
52  internal class WindowsMessageLoop : IDisposable
53  {
54  private IntPtr controlHandle;
55  private Control control;
56  private bool isControlAlive;
57  private bool switchControl;
58 
59  /// <summary>
60  /// Initializes a new instance of the <see cref="WindowsMessageLoop"/> class.
61  /// </summary>
62  public WindowsMessageLoop() {}
63 
64  /// <summary>
65  /// Initializes a new instance of the <see cref="WindowsMessageLoop"/> class.
66  /// </summary>
67  public WindowsMessageLoop(Control control)
68  {
69  Control = control;
70  }
71 
72  /// <summary>
73  /// Gets or sets the control to associate with the current render loop.
74  /// </summary>
75  /// <value>The control.</value>
76  /// <exception cref="System.InvalidOperationException">Control is already disposed</exception>
77  public Control Control
78  {
79  get
80  {
81  return control;
82  }
83  set
84  {
85  if(control == value) return;
86 
87  // Remove any previous control
88  if(control != null && !switchControl)
89  {
90  isControlAlive = false;
91  control.Disposed -= ControlDisposed;
92  controlHandle = IntPtr.Zero;
93  }
94 
95  if (value != null && value.IsDisposed)
96  {
97  throw new InvalidOperationException("Control is already disposed");
98  }
99 
100  control = value;
101  switchControl = true;
102  }
103  }
104 
105  /// <summary>
106  /// Gets or sets a value indicating whether the render loop should use the default <see cref="Application.DoEvents"/> instead of a custom window message loop lightweight for GC. Default is false.
107  /// </summary>
108  /// <value><c>true</c> if the render loop should use the default <see cref="Application.DoEvents"/> instead of a custom window message loop (default false); otherwise, <c>false</c>.</value>
109  /// <remarks>By default, RenderLoop is using a custom window message loop that is more lightweight than <see cref="Application.DoEvents" /> to process windows event message.
110  /// Set this parameter to true to use the default <see cref="Application.DoEvents"/>.</remarks>
111  public bool UseApplicationDoEvents { get; set; }
112 
113  /// <summary>
114  /// Gets or sets a value indicating whether [allow windowss keys].
115  /// </summary>
116  /// <value><c>true</c> if [allow windowss keys]; otherwise, <c>false</c>.</value>
117  public bool AllowWindowssKeys { get; set; }
118 
119  /// <summary>
120  /// Calls this method on each frame.
121  /// </summary>
122  /// <returns><c>true</c> if if the control is still active, <c>false</c> otherwise.</returns>
123  /// <exception cref="System.InvalidOperationException">An error occured </exception>
124  public bool NextFrame()
125  {
126  // Setup new control
127  // TODO this is not completely thread-safe. We should use a lock to handle this correctly
128  if (switchControl && control != null)
129  {
130  controlHandle = control.Handle;
131  control.Disposed += ControlDisposed;
132  isControlAlive = true;
133  switchControl = false;
134  }
135 
136  if(isControlAlive)
137  {
138  if(UseApplicationDoEvents)
139  {
140  // Revert back to Application.DoEvents in order to support Application.AddMessageFilter
141  // Seems that DoEvents is compatible with Mono unlike Application.Run that was not running
142  // correctly.
143  Application.DoEvents();
144  }
145  else
146  {
147  var gameForm = Control as GameForm;
148 
149  var localHandle = controlHandle;
150  if (localHandle != IntPtr.Zero)
151  {
152  // Previous code not compatible with Application.AddMessageFilter but faster then DoEvents
153  NativeMessage msg;
154  while (Win32Native.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0) != 0)
155  {
156  if (Win32Native.GetMessage(out msg, IntPtr.Zero, 0, 0) == -1)
157  {
158  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
159  "An error happened in rendering loop while processing windows messages. Error: {0}",
160  Marshal.GetLastWin32Error()));
161  }
162 
163  // NCDESTROY event?
164  if (msg.msg == 130)
165  {
166  isControlAlive = false;
167  }
168 
169  var message = new Message() { HWnd = msg.handle, LParam = msg.lParam, Msg = (int)msg.msg, WParam = msg.wParam };
170 
171  // Skip special message
172  //if (gameForm != null && message.HWnd == gameForm.Handle && gameForm.FilterWindowsKeys(ref message))
173  //{
174  // continue;
175  //}
176 
177  if (!Application.FilterMessage(ref message))
178  {
179  Win32Native.TranslateMessage(ref msg);
180  Win32Native.DispatchMessage(ref msg);
181  }
182  }
183  }
184  }
185  }
186 
187  return isControlAlive || switchControl;
188  }
189 
190  private void ControlDisposed(object sender, EventArgs e)
191  {
192  isControlAlive = false;
193  }
194 
195  /// <summary>
196  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
197  /// </summary>
198  public void Dispose()
199  {
200  Control = null;
201  }
202 
203  /// <summary>
204  /// Delegate for the rendering loop.
205  /// </summary>
206  public delegate void RenderCallback();
207 
208  /// <summary>
209  /// Runs the specified main loop in the specified context.
210  /// </summary>
211  public static void Run(ApplicationContext context, RenderCallback renderCallback)
212  {
213  Run(context.MainForm, renderCallback);
214  }
215 
216  /// <summary>
217  /// Runs the specified main loop for the specified windows form.
218  /// </summary>
219  /// <param name="form">The form.</param>
220  /// <param name="renderCallback">The rendering callback.</param>
221  /// <param name="useApplicationDoEvents">if set to <c>true</c> indicating whether the render loop should use the default <see cref="Application.DoEvents"/> instead of a custom window message loop lightweight for GC. Default is false.</param>
222  /// <exception cref="System.ArgumentNullException">form
223  /// or
224  /// renderCallback</exception>
225  public static void Run(Control form, RenderCallback renderCallback, bool useApplicationDoEvents = false)
226  {
227  if(form == null) throw new ArgumentNullException("form");
228  if(renderCallback == null) throw new ArgumentNullException("renderCallback");
229 
230  form.Show();
231  using (var renderLoop = new WindowsMessageLoop(form) { UseApplicationDoEvents = useApplicationDoEvents })
232  {
233  while(renderLoop.NextFrame())
234  {
235  renderCallback();
236  }
237  }
238  }
239 
240  /// <summary>
241  /// Gets a value indicating whether this instance is application idle.
242  /// </summary>
243  /// <value>
244  /// <c>true</c> if this instance is application idle; otherwise, <c>false</c>.
245  /// </value>
246  public static bool IsIdle
247  {
248  get
249  {
250  NativeMessage msg;
251  return (bool)(Win32Native.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0) == 0);
252  }
253  }
254  }
255 }
256 #endif