comment on timer checking, are timers always sorted by triggertime? Else the code...
[freeglut] / src / fg_main.c
1 /*
2  * freeglut_main.c
3  *
4  * The windows message processing methods.
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Creation date: Fri Dec 3 1999
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26  */
27
28 #include <GL/freeglut.h>
29 #include "fg_internal.h"
30 #ifdef HAVE_ERRNO_H
31 #    include <errno.h>
32 #endif
33 #include <stdarg.h>
34 #ifdef  HAVE_VFPRINTF
35 #    define VFPRINTF(s,f,a) vfprintf((s),(f),(a))
36 #elif defined(HAVE__DOPRNT)
37 #    define VFPRINTF(s,f,a) _doprnt((f),(a),(s))
38 #else
39 #    define VFPRINTF(s,f,a)
40 #endif
41
42 /*
43  * Try to get the maximum value allowed for ints, falling back to the minimum
44  * guaranteed by ISO C99 if there is no suitable header.
45  */
46 #ifdef HAVE_LIMITS_H
47 #    include <limits.h>
48 #endif
49 #ifndef INT_MAX
50 #    define INT_MAX 32767
51 #endif
52
53 #ifndef MIN
54 #    define MIN(a,b) (((a)<(b)) ? (a) : (b))
55 #endif
56
57 extern void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height );
58 extern void fgPlatformDisplayWindow ( SFG_Window *window );
59 extern fg_time_t fgPlatformSystemTime ( void );
60 extern void fgPlatformSleepForEvents( fg_time_t msec );
61 extern void fgPlatformProcessSingleEvent ( void );
62 extern void fgPlatformMainLoopPreliminaryWork ( void );
63
64
65
66
67 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
68
69 static void fghReshapeWindow ( SFG_Window *window, int width, int height )
70 {
71     SFG_Window *current_window = fgStructure.CurrentWindow;
72
73     freeglut_return_if_fail( window != NULL );
74
75         fgPlatformReshapeWindow ( window, width, height );
76
77     if( FETCH_WCB( *window, Reshape ) )
78         INVOKE_WCB( *window, Reshape, ( width, height ) );
79     else
80     {
81         fgSetWindow( window );
82         glViewport( 0, 0, width, height );
83     }
84
85     /*
86      * Force a window redraw.  In Windows at least this is only a partial
87      * solution:  if the window is increasing in size in either dimension,
88      * the already-drawn part does not get drawn again and things look funny.
89      * But without this we get this bad behaviour whenever we resize the
90      * window.
91      * DN: Hmm.. the above sounds like a concern only in single buffered mode...
92      */
93     window->State.Redisplay = GL_TRUE;
94
95     if( window->IsMenu )
96         fgSetWindow( current_window );
97 }
98
99 /*
100  * Calls a window's redraw method. This is used when
101  * a redraw is forced by the incoming window messages.
102  */
103 void fghRedrawWindow ( SFG_Window *window )
104 {
105     SFG_Window *current_window = fgStructure.CurrentWindow;
106
107     freeglut_return_if_fail( window );
108
109     if( window->State.NeedToInitContext ) {
110         INVOKE_WCB( *window, InitContext, ());
111         window->State.NeedToInitContext = GL_FALSE;
112     }
113
114     freeglut_return_if_fail( FETCH_WCB ( *window, Display ) );
115
116     window->State.Redisplay = GL_FALSE;
117
118     freeglut_return_if_fail( window->State.Visible );
119
120     fgSetWindow( window );
121
122     if( window->State.NeedToResize )
123     {
124         /* Set need to resize to false before calling fghReshapeWindow, otherwise
125            in the case the user's reshape callback calls glutReshapeWindow,
126            his request would get canceled after fghReshapeWindow gets called.
127          */
128         window->State.NeedToResize = GL_FALSE;
129
130         fghReshapeWindow(
131             window,
132             window->State.Width,
133             window->State.Height
134         );
135     }
136
137     INVOKE_WCB( *window, Display, ( ) );
138
139     fgSetWindow( current_window );
140 }
141
142
143 static void fghcbDisplayWindow( SFG_Window *window,
144                                 SFG_Enumerator *enumerator )
145 {
146     if( window->State.Redisplay &&
147         window->State.Visible )
148     {
149         window->State.Redisplay = GL_FALSE;
150                 fgPlatformDisplayWindow ( window );
151     }
152
153     fgEnumSubWindows( window, fghcbDisplayWindow, enumerator );
154 }
155
156 /*
157  * Make all windows perform a display call
158  */
159 static void fghDisplayAll( void )
160 {
161     SFG_Enumerator enumerator;
162
163     enumerator.found = GL_FALSE;
164     enumerator.data  =  NULL;
165
166     fgEnumWindows( fghcbDisplayWindow, &enumerator );
167 }
168
169 /*
170  * Window enumerator callback to check for the joystick polling code
171  */
172 static void fghcbCheckJoystickPolls( SFG_Window *window,
173                                      SFG_Enumerator *enumerator )
174 {
175     fg_time_t checkTime;
176     
177     if (window->State.JoystickPollRate > 0 && FETCH_WCB( *window, Joystick ))
178     {
179         /* This window has a joystick to be polled (if pollrate <= 0, user needs to poll manually with glutForceJoystickFunc */
180         checkTime= fgElapsedTime( );
181
182         if( window->State.JoystickLastPoll + window->State.JoystickPollRate <=
183             checkTime )
184         {
185 #if !defined(_WIN32_WCE)
186             fgJoystickPollWindow( window );
187 #endif /* !defined(_WIN32_WCE) */
188             window->State.JoystickLastPoll = checkTime;
189         }
190     }
191
192     fgEnumSubWindows( window, fghcbCheckJoystickPolls, enumerator );
193 }
194
195 /*
196  * Check all windows for joystick polling
197  * 
198  * The real way to do this is to make use of the glutTimer() API
199  * to more cleanly re-implement the joystick API.  Then, this code
200  * and all other "joystick timer" code can be yanked.
201  */
202 static void fghCheckJoystickPolls( void )
203 {
204     SFG_Enumerator enumerator;
205
206     enumerator.found = GL_FALSE;
207     enumerator.data  =  NULL;
208
209     fgEnumWindows( fghcbCheckJoystickPolls, &enumerator );
210 }
211
212 /*
213  * Check the global timers
214  */
215 static void fghCheckTimers( void )
216 {
217     fg_time_t checkTime = fgElapsedTime( );
218
219     while( fgState.Timers.First )
220     {
221         SFG_Timer *timer = fgState.Timers.First;
222
223         if( timer->TriggerTime > checkTime )
224             /* XXX: are timers always sorted by triggerTime? If not, this and fghNextTimer are wrong */
225             break;
226
227         fgListRemove( &fgState.Timers, &timer->Node );
228         fgListAppend( &fgState.FreeTimers, &timer->Node );
229
230         timer->Callback( timer->ID );
231     }
232 }
233
234  
235 /* Platform-dependent time in milliseconds, as an unsigned 64-bit integer.
236  * This doesn't overflow in any reasonable time, so no need to worry about
237  * that. The GLUT API return value will however overflow after 49.7 days,
238  * which means you will still get in trouble when running the
239  * application for more than 49.7 days.
240  */  
241 fg_time_t fgSystemTime(void)
242 {
243         return fgPlatformSystemTime();
244 }
245   
246 /*
247  * Elapsed Time
248  */
249 fg_time_t fgElapsedTime( void )
250 {
251     return fgSystemTime() - fgState.Time;
252 }
253
254 /*
255  * Error Messages.
256  */
257 void fgError( const char *fmt, ... )
258 {
259     va_list ap;
260
261     if (fgState.ErrorFunc) {
262
263         va_start( ap, fmt );
264
265         /* call user set error handler here */
266         fgState.ErrorFunc(fmt, ap);
267
268         va_end( ap );
269
270     } else {
271 #if FREEGLUT_ERRORS
272         va_start( ap, fmt );
273
274         fprintf( stderr, "freeglut ");
275         if( fgState.ProgramName )
276             fprintf( stderr, "(%s): ", fgState.ProgramName );
277         VFPRINTF( stderr, fmt, ap );
278         fprintf( stderr, "\n" );
279
280         va_end( ap );
281 #endif
282
283         if ( fgState.Initialised )
284             fgDeinitialize ();
285
286         exit( 1 );
287     }
288 }
289
290 void fgWarning( const char *fmt, ... )
291 {
292     va_list ap;
293
294     if (fgState.WarningFunc) {
295
296         va_start( ap, fmt );
297
298         /* call user set warning handler here */
299         fgState.WarningFunc(fmt, ap);
300
301         va_end( ap );
302
303     } else {
304 #if FREEGLUT_WARNINGS
305         va_start( ap, fmt );
306
307         fprintf( stderr, "freeglut ");
308         if( fgState.ProgramName )
309             fprintf( stderr, "(%s): ", fgState.ProgramName );
310         VFPRINTF( stderr, fmt, ap );
311         fprintf( stderr, "\n" );
312
313         va_end( ap );
314 #endif
315     }
316 }
317
318
319 /*
320  * Indicates whether Joystick events are being used by ANY window.
321  *
322  * The current mechanism is to walk all of the windows and ask if
323  * there is a joystick callback.  We have a short-circuit early
324  * return if we find any joystick handler registered.
325  *
326  *
327  */
328 static void fghHavePendingRedisplaysCallback( SFG_Window* w, SFG_Enumerator* e)
329 {
330     if( w->State.Redisplay && w->State.Visible )
331     {
332         e->found = GL_TRUE;
333         e->data = w;
334     }
335     fgEnumSubWindows( w, fghHavePendingRedisplaysCallback, e );
336 }
337 static int fghHavePendingRedisplays (void)
338 {
339     SFG_Enumerator enumerator;
340
341     enumerator.found = GL_FALSE;
342     enumerator.data = NULL;
343     fgEnumWindows( fghHavePendingRedisplaysCallback, &enumerator );
344     return !!enumerator.data;
345 }
346
347 /*
348  * Returns the number of GLUT ticks (milliseconds) till the next timer event.
349  */
350 static fg_time_t fghNextTimer( void )
351 {
352     fg_time_t currentTime = fgElapsedTime();
353     SFG_Timer *timer = fgState.Timers.First;
354
355     if( !timer )
356         return INT_MAX;
357
358     if( timer->TriggerTime < currentTime )
359         return 0;
360     else
361         return timer->TriggerTime - currentTime;
362 }
363
364 static void fghSleepForEvents( void )
365 {
366     fg_time_t msec;
367
368     if( fghHavePendingRedisplays( ) )
369         return;
370
371     msec = fghNextTimer( );
372     /* XXX Should use GLUT timers for joysticks... */
373     /* XXX Dumb; forces granularity to .01sec */
374     if( fgState.NumActiveJoysticks>0 && ( msec > 10 ) )     
375         msec = 10;
376
377         fgPlatformSleepForEvents ( msec );
378 }
379
380
381 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
382
383 /*
384  * Executes a single iteration in the freeglut processing loop.
385  */
386 void FGAPIENTRY glutMainLoopEvent( void )
387 {
388         fgPlatformProcessSingleEvent ();
389
390     if( fgState.Timers.First )
391         fghCheckTimers( );
392     if (fgState.NumActiveJoysticks>0)   /* If zero, don't poll joysticks */
393         fghCheckJoystickPolls( );
394     fghDisplayAll( );
395
396     fgCloseWindows( );
397 }
398
399 /*
400  * Enters the freeglut processing loop.
401  * Stays until the "ExecState" changes to "GLUT_EXEC_STATE_STOP".
402  */
403 void FGAPIENTRY glutMainLoop( void )
404 {
405     int action;
406
407     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutMainLoop" );
408
409     if (!fgStructure.Windows.First)
410         fgError(" ERROR:  glutMainLoop called with no windows created.");
411
412         fgPlatformMainLoopPreliminaryWork ();
413
414     fgState.ExecState = GLUT_EXEC_STATE_RUNNING ;
415     while( fgState.ExecState == GLUT_EXEC_STATE_RUNNING )
416     {
417         SFG_Window *window;
418
419         glutMainLoopEvent( );
420         /*
421          * Step through the list of windows, seeing if there are any
422          * that are not menus
423          */
424         for( window = ( SFG_Window * )fgStructure.Windows.First;
425              window;
426              window = ( SFG_Window * )window->Node.Next )
427             if ( ! ( window->IsMenu ) )
428                 break;
429
430         if( ! window )
431             fgState.ExecState = GLUT_EXEC_STATE_STOP;
432         else
433         {
434             if( fgState.IdleCallback )
435             {
436                 if( fgStructure.CurrentWindow &&
437                     fgStructure.CurrentWindow->IsMenu )
438                     /* fail safe */
439                     fgSetWindow( window );
440                 fgState.IdleCallback( );
441             }
442             else
443                 fghSleepForEvents( );
444         }
445     }
446
447     /*
448      * When this loop terminates, destroy the display, state and structure
449      * of a freeglut session, so that another glutInit() call can happen
450      *
451      * Save the "ActionOnWindowClose" because "fgDeinitialize" resets it.
452      */
453     action = fgState.ActionOnWindowClose;
454     fgDeinitialize( );
455     if( action == GLUT_ACTION_EXIT )
456         exit( 0 );
457 }
458
459 /*
460  * Leaves the freeglut processing loop.
461  */
462 void FGAPIENTRY glutLeaveMainLoop( void )
463 {
464     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutLeaveMainLoop" );
465     fgState.ExecState = GLUT_EXEC_STATE_STOP ;
466 }
467
468
469
470 /*** END OF FILE ***/