Hermes2D  3.0
view_support.cpp
1 // This file is part of Hermes2D.
2 //
3 // Hermes2D is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 2 of the License, or
6 // (at your option) any later version.
7 //
8 // Hermes2D is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Hermes2D. If not, see <http://www.gnu.org/licenses/>.
15 
16 #ifndef NOGLUT
17 
18 #include <GL/glew.h>
19 #include <GL/freeglut.h>
20 #ifndef _WINDOWS
21 #include <sys/time.h>
22 #include <unistd.h>
23 #endif
24 
25 #include <map>
26 #include <vector>
27 #include "global.h"
28 #include "view.h"
29 #include "view_support.h"
30 
31 namespace Hermes
32 {
33  namespace Hermes2D
34  {
35  namespace Views
36  {
37  // Synchronization.
39 
40  /* monitor */
41  ViewMonitor::ViewMonitor()
42  {
43  pthread_mutexattr_init(&mutex_attr);
44  pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
45  pthread_mutex_init(&mutex, &mutex_attr);
46 
47  pthread_cond_init(&cond_keypress, nullptr);
48  pthread_cond_init(&cond_close, nullptr);
49  pthread_cond_init(&cond_drawing_finished, nullptr);
50  pthread_cond_init(&cond_cross_thread_call, nullptr);
51  }
52 
53  ViewMonitor::~ViewMonitor()
54  {
55  pthread_mutex_destroy(&mutex);
56  pthread_mutexattr_destroy(&mutex_attr);
57  pthread_cond_destroy(&cond_keypress);
58  pthread_cond_destroy(&cond_close);
59  pthread_cond_destroy(&cond_drawing_finished);
60  pthread_cond_destroy(&cond_cross_thread_call);
61  }
62 
63  /* forward definitions */
64  // True if GLUT is initialized
65  static bool glut_initialized = false;
66  // True if GLEW is initialized
67  static bool glew_initialized = false;
68 
69  /* threading */
70  struct ThreadInfo { //< Thread information
71  // View thread.
72  pthread_t thread;
73  // True if view thread should end
74  bool should_quit;
75  ThreadInfo() : should_quit(false) {};
76  };
77 
78  // Current view thread.
79  static ThreadInfo* view_thread;
80 
81  typedef int(*CTC_FUNC)(void*);
82  static CTC_FUNC ctc_function = nullptr;
83  static void* ctc_param;
84  static int ctc_result;
85  // Instances of views.
86  static std::map<int, View*> view_instances;
87 
88  struct ViewParams { //< Parameters used to initialize view
89  View* view;
90  int x, y;
91  int width, height;
92  const char* title;
93  ViewParams(View* view, int x, int y, int width, int height, const char* title) : view(view), x(x), y(y), width(width), height(height), title(title) {};
94  };
95 
96  struct RemoveParams { //< Parameters used to remove view
97  int view_id;
98  // True to destroy glut window.
99  bool destroy_glut_window;
100  RemoveParams(int view_id, bool destroy_glut_window) : view_id(view_id), destroy_glut_window(destroy_glut_window) {};
101  };
102 
103  struct TitleParams { //< Parameters of a title
104  int view_id;
105  const char* title;
106  TitleParams(int view_id, const char* title) : view_id(view_id), title(title) {};
107  };
108 
109  /* view thread function */
110  static void* view_thread_func(void* param)
111  {
112  ThreadInfo* thread_info = (ThreadInfo*)param;
113 
114  //initize GLUT
115  init_glut();
116 
117  //run message loop
118  while (1)
119  {
120  //handle glut messages
121  glutMainLoopEvent();
122 
123  //check whether to quit
124  if (thread_info->should_quit)
125  break;
126 
127  //handle CTC
128  view_sync.enter();
129  if (ctc_function != nullptr)
130  {
131  ctc_result = ctc_function(ctc_param);
132  ctc_function = nullptr;
133  view_sync.signal_cross_thread_call();
134  }
135  view_sync.leave();
136 
137  //wait for 10 ms
138 #ifdef _WINDOWS
139  Sleep(10);
140 #else
141  usleep(10 * 1000);
142 #endif
143  }
144 
145  //cleanup
146  shutdown_glut();
147 
148  //cleanup
149  delete thread_info;
150  return nullptr;
151  }
152 
153  // Returns true, if outside the thread.
154  static bool need_safe_call() { return pthread_equal(view_thread->thread, pthread_self()) == 0; }
155 
156  // Does a cross thread call.
157  static int call_in_thread(CTC_FUNC func, void* param)
158  {
159  //check whether the thread is running if not start it
160  view_sync.enter();
161  if (view_thread == nullptr)
162  {
163  ThreadInfo* new_thread_info = nullptr;
164  try { new_thread_info = new ThreadInfo(); }
165  catch (std::bad_alloc&) { throw Hermes::Exceptions::Exception("Failed to allocate structure for view thread"); }
166  int err = pthread_create(&new_thread_info->thread, nullptr, view_thread_func, new_thread_info);
167  if (err)
168  {
169  delete new_thread_info;
170  throw Hermes::Exceptions::Exception("Failed to create main thread, error: %d", err);
171  }
172  view_thread = new_thread_info;
173  }
174  view_sync.leave();
175 
176  //make call
177  if (need_safe_call())
178  {
179  view_sync.enter();
180  ctc_function = func;
181  ctc_param = param;
182  view_sync.wait_cross_thread_call();
183  int result = ctc_result;
184  view_sync.leave();
185  return result;
186  }
187  else
188  return func(param);
189  }
190 
191  // Sets a title of a view. Function has to be called just from the inside of view thread with a locked sync_view.
192  static int set_view_title_in_thread(void* title_pars_ptr)
193  {
194  TitleParams& title_params = *((TitleParams*)title_pars_ptr);
195  std::map<int, View*>::iterator found_view = view_instances.find(title_params.view_id);
196  if (found_view == view_instances.end())
197  {
198  throw Exceptions::Exception("Settings title of a view that is not registered.");
199  return -1;
200  }
201 
202  //create GLUT window
203  glutSetWindow(title_params.view_id);
204  glutSetWindowTitle(title_params.title);
205 
206  return 0;
207  }
208 
209  // Adds a new_ view. Function has to be called just from the inside of view thread with a locked sync_view.
210  int add_view_in_thread(void* view_pars_ptr)
211  {
212  ViewParams& view_params = *((ViewParams*)view_pars_ptr);
213 
214  //create GLUT window
215  glutInitWindowPosition(view_params.x, view_params.y);
216  glutInitWindowSize(view_params.width, view_params.height);
217  int view_id = glutCreateWindow(view_params.title);
218  glutSetWindowData(view_params.view);
219 
220  //initialize GLEW
221  GLenum err = glewInit();
222  if (err != GLEW_OK)
223  throw Exceptions::Exception("GLEW error: %s", glewGetErrorString(err));
224  glew_initialized = true;
225 
226  //register callbacks
227  glutDisplayFunc(on_display_stub);
228  glutReshapeFunc(on_reshape_stub);
229  glutMotionFunc(on_mouse_move_stub);
230  glutPassiveMotionFunc(on_mouse_move_stub);
231  glutMouseFunc(on_mouse_click_stub);
232  glutKeyboardFunc(on_key_down_stub);
233  glutSpecialFunc(on_special_key_stub);
234  glutEntryFunc(on_entry_stub);
235  glutCloseFunc(on_close_stub);
236 
237  //add to structures
238  view_instances[view_id] = view_params.view;
239 
240  //call handlers
241  view_params.view->on_create(view_id);
242 
243  return view_id;
244  }
245 
246  // Removes a new_ view. Function has to be called just from the inside of view thread with a locked sync_view.
247  int remove_view_in_thread(void* remove_params_ptr)
248  {
249  RemoveParams& params = *(RemoveParams*)remove_params_ptr;
250  std::map<int, View*>::iterator found_view = view_instances.find(params.view_id);
251  if (found_view == view_instances.end())
252  {
253  throw Exceptions::Exception("Removing of a view that is not registered");
254  return -1;
255  }
256 
257  //destroy window if requested (it will not be requested when remove is called as a reaction to on_close)
258  if (params.destroy_glut_window)
259  {
260  //remove window from GLUT
261  glutSetWindow(params.view_id);
262  //prevent stubs from being executed if there is still some message waiting for the window
263  glutSetWindowData(nullptr);
264 
265  //call on-close event
266  found_view->second->on_close();
267 
268  //finish removal of window from GLUT
269  glutDestroyWindow(params.view_id);
270  }
271 
272  //remove from structures
273  view_instances.erase(found_view);
274 
275  //thread cleanup
276  if (view_instances.empty())
277  {
278  view_thread->should_quit = true;
279  view_thread = nullptr;
280 
281  //signal all events
282  view_sync.signal_close();
283  view_sync.signal_keypress();
284  view_sync.signal_drawing_finished();
285  }
286 
287  return 0;
288  }
289 
290  // Forces a redisplay of a view. Function has to be called just from the inside of view thread.
291  static int refresh_view_in_thread(void* view_id_ptr)
292  {
293  int view_id = *((int*)view_id_ptr);
294  std::map<int, View*>::iterator found_view = view_instances.find(view_id);
295  if (found_view == view_instances.end())
296  throw Exceptions::Exception("Refreshing a view that is not registered");
297 
298  //redisplay
299  if (found_view != view_instances.end())
300  {
301  glutSetWindow(view_id);
302  glutPostRedisplay();
303  }
304 
305  return 0;
306  }
307 
308  /* GLUT */
309  // Length of the double-click time. (FIXME: get double-click time for Linux)
310  static long double_click_delay_ms = 300;
311 
312 #define STUB_GET_VIEW() View* wnd = (View*)glutGetWindowData() /* retrieves view for the current GLUT callback */
313 #define STUB_CALL(__call) STUB_GET_VIEW(); if(wnd != nullptr) __call; /* calls a method of a view for the current GLUT callbakc */
314 
315  void on_display_stub(void) { STUB_CALL(wnd->pre_display()); }
316  void on_reshape_stub(int width, int height) { STUB_CALL(wnd->on_reshape(width, height)); }
317  void on_mouse_move_stub(int x, int y) { STUB_CALL(wnd->on_mouse_move(x, y)); }
318  void on_key_down_stub(unsigned char key, int x, int y) { STUB_CALL(wnd->on_key_down(key, x, y)); }
319  void on_special_key_stub(int key, int x, int y) { STUB_CALL(wnd->on_special_key(key, x, y)); }
320  void on_entry_stub(int state) { STUB_CALL(wnd->on_entry(state)); }
321  void on_mouse_click_stub(int button, int state, int x, int y)
322  {
323  STUB_GET_VIEW();
324  if (wnd == nullptr)
325  return;
326 
327  // emulate double-click messages
328  if (state == GLUT_DOWN)
329  {
330  static double last_tick = 0;
331  double tick = View::get_tick_count();
332  //if(tick < last_tick) //todo
333  if (tick - last_tick < double_click_delay_ms)
334  {
335  if (button == GLUT_LEFT_BUTTON)
336  wnd->on_left_mouse_double_click(x, y);
337  else if (button == GLUT_RIGHT_BUTTON)
338  wnd->on_right_mouse_double_click(x, y);
339  else
340  wnd->on_middle_mouse_double_click(x, y);
341 
342  last_tick = 0;
343  return;
344  }
345  last_tick = tick;
346  }
347 
348  // call proper click handler
349  if (button == GLUT_LEFT_BUTTON)
350  {
351  if (state == GLUT_DOWN)
352  wnd->on_left_mouse_down(x, y);
353  else
354  wnd->on_left_mouse_up(x, y);
355  }
356  else if (button == GLUT_RIGHT_BUTTON)
357  {
358  if (state == GLUT_DOWN)
359  wnd->on_right_mouse_down(x, y);
360  else
361  wnd->on_right_mouse_up(x, y);
362  }
363  else
364  {
365  if (state == GLUT_DOWN)
366  wnd->on_middle_mouse_down(x, y);
367  else
368  wnd->on_middle_mouse_up(x, y);
369  }
370  }
371  void on_close_stub()
372  {
373  STUB_GET_VIEW();
374  if (wnd == nullptr)
375  return;
376 
377  //call callback
378  wnd->on_close();
379 
380  //remove view from system
381  view_sync.enter();
382  RemoveParams params(glutGetWindow(), false);
383  remove_view_in_thread(&params);
384  view_sync.signal_close();
385  view_sync.leave();
386  }
387 
388  //initialize GLUT
389  bool init_glut()
390  {
391  static int argc = 1;
392  static const char* argv[1] = { "x" };
393 
394  //prepare GLUT environment
395  if (!glut_initialized)
396  {
397  glutInit(&argc, (char**)argv);
398  glut_initialized = true;
399  }
400  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_ACCUM);
401  glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
402 
403  //obtain other parameters
404 #ifdef _WINDOWS
405  double_click_delay_ms = GetDoubleClickTime();
406 #endif
407 
408  return true;
409  }
410 
411  // shutdowns GLUT
413  {
414  return true;
415  }
416 
417  /* public functions */
418  void set_view_title(int view_id, const char* title)
419  {
420  TitleParams params(view_id, title);
421  call_in_thread(set_view_title_in_thread, &params);
422  }
423 
424  int add_view(View* view, int x, int y, int width, int height, const char* title)
425  {
426  ViewParams params(view, x, y, width, height, title);
427  return call_in_thread(add_view_in_thread, &params);
428  }
429 
430  void refresh_view(int view_id) { call_in_thread(refresh_view_in_thread, &view_id); }
431 
432  void remove_view(int view_id)
433  {
434  RemoveParams params(view_id, true);
435  call_in_thread(remove_view_in_thread, &params);
436  }
437 
438  void force_view_thread_shutdown()
439  {
440  pthread_t current_thread;
441  bool should_wait = false;
442 
443  view_sync.enter();
444 
445  //destroy all views
446  std::map<int, View*>::const_iterator iter = view_instances.begin();
447  while (iter != view_instances.end())
448  {
449  glutDestroyWindow(iter->first);
450  ++iter;
451  }
452  view_instances.clear();
453 
454  //tell thread to finish
455  if (view_thread != nullptr)
456  {
457  current_thread = view_thread->thread;
458  view_thread->should_quit = true;
459  view_thread = nullptr;
460  }
461  view_sync.leave();
462 
463  //wait for thread to finish
464  if (should_wait)
465  {
466  glew_initialized = false;
467  pthread_join(current_thread, nullptr);
468  }
469  }
470 
471  void wait_for_all_views_close(const char* text)
472  {
473  pthread_t current_thread;
474  bool should_wait = false;
475 
476  //tell thread to finish
477  view_sync.enter();
478  if (view_thread != nullptr)
479  {
480  current_thread = view_thread->thread;
481  should_wait = true;
482  }
483  view_sync.leave();
484 
485  //wait for thread to finish
486  if (should_wait)
487  {
488  fprintf(stdout, "%s", text); fflush(stdout);
489  pthread_join(current_thread, nullptr);
490  }
491  }
492 
493  void wait_for_any_key(const char* text)
494  {
495  //wait for key
496  view_sync.enter();
497  if (view_thread != nullptr)
498  {
499  fprintf(stdout, "%s", text); fflush(stdout);
500  view_sync.wait_keypress();
501  }
502  view_sync.leave();
503  }
504  }
505  }
506 }
507 #endif
Definition: adapt.h:24
void refresh_view(int view_id)
Forces redisplay of a view.
void remove_view(int view_id)
Removes a view.
pthread_cond_t cond_drawing_finished
Condition used to signal that drawing has finished.
Definition: view_support.h:48
pthread_mutex_t mutex
Mutex that protects monitor.
Definition: view_support.h:40
Represents a simple visualization window.
Definition: view.h:80
void signal_drawing_finished()
signals drawing finished inside a protected section
Definition: view_support.h:65
Common definitions for Hermes2D.
void signal_close()
signals close inside a protected section
Definition: view_support.h:61
void leave()
leaves protected section
Definition: view_support.h:55
File containing View abstract class.
void enter()
enters protected section
Definition: view_support.h:53
ViewMonitor view_sync
synchronization between all views. Used to access OpenGL and signal a window close event and a keypre...
int add_view(View *view, int x, int y, int width, int height, const char *title)
Adds a view.
void wait_for_any_key(const char *text)
Waits for a keypress which is not processed.
void wait_keypress()
waits for keypress inside a protected section
Definition: view_support.h:59
pthread_cond_t cond_cross_thread_call
Condition used to signal a cross-thread call.
Definition: view_support.h:42
void signal_keypress()
signals keypress inside a protected section
Definition: view_support.h:57
void wait_cross_thread_call()
waits for finishing of a cross-thread call
Definition: view_support.h:71
void signal_cross_thread_call()
signals that cross-thread-call finished
Definition: view_support.h:69
static double get_tick_count()
returns a current time[in ms]
Definition: view.cpp:537
HERMES_API bool init_glut()
Initialize GLUT.
pthread_cond_t cond_close
Condition used to signal close of a window.
Definition: view_support.h:46
< A monitor used to synchronize thread in views.
Definition: view_support.h:34
void wait_for_all_views_close(const char *text)
Forces view thread to shutdown.
pthread_cond_t cond_keypress
Condition used to signal a keypress.
Definition: view_support.h:44
void set_view_title(int view_id, const char *title)
Sets title of a view.
pthread_mutexattr_t mutex_attr
Mutext attributes.
Definition: view_support.h:38
HERMES_API bool shutdown_glut()
Shutdown GLUT.