Add warning about calling synchronous queue in callback to UI
[lsnes.git] / src / core / window.cpp
blob22c8fae47c2765e298f67cf1f6fa2b7a9782bf85
1 #include "core/command.hpp"
2 #include "core/dispatch.hpp"
3 #include "core/framerate.hpp"
4 #include "core/misc.hpp"
5 #include "core/render.hpp"
6 #include "core/window.hpp"
8 #include <fstream>
9 #include <iostream>
10 #include <string>
11 #include <deque>
12 #include <boost/iostreams/categories.hpp>
13 #include <boost/iostreams/copy.hpp>
14 #include <boost/iostreams/stream.hpp>
15 #include <boost/iostreams/stream_buffer.hpp>
16 #include <boost/iostreams/filter/symmetric.hpp>
17 #include <boost/iostreams/filter/zlib.hpp>
18 #include <boost/iostreams/filtering_stream.hpp>
19 #include <boost/iostreams/device/back_inserter.hpp>
21 #define MAXMESSAGES 5000
22 #define INIT_WIN_SIZE 6
24 mutex::holder::holder(mutex& m) throw()
25 : mut(m)
27 mut.lock();
30 mutex::holder::~holder() throw()
32 mut.unlock();
35 mutex::~mutex() throw()
39 mutex::mutex() throw()
43 condition::~condition() throw()
47 mutex& condition::associated() throw()
49 return assoc;
52 condition::condition(mutex& m)
53 : assoc(m)
57 thread_id::thread_id() throw()
61 thread_id::~thread_id() throw()
65 thread::thread() throw()
67 alive = true;
68 joined = false;
71 thread::~thread() throw()
75 bool thread::is_alive() throw()
77 return alive;
80 void* thread::join() throw()
82 if(!joined)
83 this->_join();
84 joined = true;
85 return returns;
88 void thread::notify_quit(void* ret) throw()
90 returns = ret;
91 alive = false;
94 keypress::keypress()
96 key1 = NULL;
97 key2 = NULL;
98 value = 0;
101 keypress::keypress(modifier_set mod, keygroup& _key, short _value)
103 modifiers = mod;
104 key1 = &_key;
105 key2 = NULL;
106 value = _value;
109 keypress::keypress(modifier_set mod, keygroup& _key, keygroup& _key2, short _value)
111 modifiers = mod;
112 key1 = &_key;
113 key2 = &_key2;
114 value = _value;
118 namespace
120 function_ptr_command<> identify_key("show-plugins", "Show plugins in use",
121 "Syntax: show-plugins\nShows plugins in use.\n",
122 []() throw(std::bad_alloc, std::runtime_error) {
123 messages << "Graphics:\t" << graphics_plugin::name << std::endl;
124 messages << "Sound:\t" << sound_plugin::name << std::endl;
125 messages << "Joystick:\t" << joystick_plugin::name << std::endl;
128 function_ptr_command<const std::string&> enable_sound("enable-sound", "Enable/Disable sound",
129 "Syntax: enable-sound <on/off>\nEnable or disable sound.\n",
130 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
131 std::string s = args;
132 if(s == "on" || s == "true" || s == "1" || s == "enable" || s == "enabled") {
133 if(!platform::sound_initialized())
134 throw std::runtime_error("Sound failed to initialize and is disabled");
135 platform::sound_enable(true);
136 } else if(s == "off" || s == "false" || s == "0" || s == "disable" || s == "disabled") {
137 if(platform::sound_initialized())
138 platform::sound_enable(false);
139 } else
140 throw std::runtime_error("Bad sound setting");
143 function_ptr_command<const std::string&> set_sound_device("set-sound-device", "Set sound device",
144 "Syntax: set-sound-device <id>\nSet sound device to <id>.\n",
145 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
146 if(!platform::sound_initialized())
147 throw std::runtime_error("Sound failed to initialize and is disabled");
148 platform::set_sound_device(args);
151 function_ptr_command<> get_sound_devices("show-sound-devices", "Show sound devices",
152 "Syntax: show-sound-devices\nShow listing of available sound devices\n",
153 []() throw(std::bad_alloc, std::runtime_error) {
154 if(!platform::sound_initialized())
155 throw std::runtime_error("Sound failed to initialize and is disabled");
156 auto r = platform::get_sound_devices();
157 auto s = platform::get_sound_device();
158 std::string dname = "unknown";
159 if(r.count(s))
160 dname = r[s];
161 messages << "Detected " << r.size() << " sound output devices." << std::endl;
162 for(auto i : r)
163 messages << "Audio device " << i.first << ": " << i.second << std::endl;
164 messages << "Currently using device " << platform::get_sound_device() << " ("
165 << dname << ")" << std::endl;
168 function_ptr_command<> get_sound_status("show-sound-status", "Show sound status",
169 "Syntax: show-sound-status\nShow current sound status\n",
170 []() throw(std::bad_alloc, std::runtime_error) {
171 messages << "Sound plugin: " << sound_plugin::name << std::endl;
172 if(!platform::sound_initialized())
173 messages << "Sound initialization failed, sound disabled" << std::endl;
174 else {
175 auto r = platform::get_sound_devices();
176 auto s = platform::get_sound_device();
177 std::string dname = "unknown";
178 if(r.count(s))
179 dname = r[s];
180 messages << "Current sound device " << s << " (" << dname << ")" << std::endl;
184 emulator_status emustatus;
186 class window_output
188 public:
189 typedef char char_type;
190 typedef boost::iostreams::sink_tag category;
191 window_output(int* dummy)
195 void close()
199 std::streamsize write(const char* s, std::streamsize n)
201 size_t oldsize = stream.size();
202 stream.resize(oldsize + n);
203 memcpy(&stream[oldsize], s, n);
204 while(true) {
205 size_t lf = stream.size();
206 for(size_t i = 0; i < stream.size(); i++)
207 if(stream[i] == '\n') {
208 lf = i;
209 break;
211 if(lf == stream.size())
212 break;
213 std::string foo(stream.begin(), stream.begin() + lf);
214 platform::message(foo);
215 if(lf + 1 < stream.size())
216 memmove(&stream[0], &stream[lf + 1], stream.size() - lf - 1);
217 stream.resize(stream.size() - lf - 1);
219 return n;
221 protected:
222 std::vector<char> stream;
225 class msgcallback : public messagebuffer::update_handler
227 public:
228 ~msgcallback() throw() {};
229 void messagebuffer_update() throw(std::bad_alloc, std::runtime_error)
231 platform::notify_message();
233 } msg_callback_obj;
235 std::ofstream system_log;
236 bool sounds_enabled = true;
239 emulator_status& platform::get_emustatus() throw()
241 return emustatus;
244 void platform::sound_enable(bool enable) throw()
246 sound_plugin::enable(enable);
247 sounds_enabled = enable;
248 information_dispatch::do_sound_unmute(enable);
251 void platform::set_sound_device(const std::string& dev) throw()
253 try {
254 sound_plugin::set_device(dev);
255 } catch(std::exception& e) {
256 out() << "Error changing sound device: " << e.what() << std::endl;
258 //After failed change, we don't know what is selected.
259 information_dispatch::do_sound_change(sound_plugin::get_device());
262 bool platform::is_sound_enabled() throw()
264 return sounds_enabled;
268 void platform::init()
270 msgbuf.register_handler(msg_callback_obj);
271 system_log.open("lsnes.log", std::ios_base::out | std::ios_base::app);
272 time_t curtime = time(NULL);
273 struct tm* tm = localtime(&curtime);
274 char buffer[1024];
275 strftime(buffer, 1023, "%Y-%m-%d %H:%M:%S %Z", tm);
276 system_log << "-----------------------------------------------------------------------" << std::endl;
277 system_log << "lsnes started at " << buffer << std::endl;
278 system_log << "-----------------------------------------------------------------------" << std::endl;
279 do_init_font();
280 graphics_plugin::init();
281 sound_plugin::init();
282 joystick_plugin::init();
285 void platform::quit()
287 joystick_plugin::quit();
288 sound_plugin::quit();
289 graphics_plugin::quit();
290 msgbuf.unregister_handler(msg_callback_obj);
291 time_t curtime = time(NULL);
292 struct tm* tm = localtime(&curtime);
293 char buffer[1024];
294 strftime(buffer, 1023, "%Y-%m-%d %H:%M:%S %Z", tm);
295 system_log << "-----------------------------------------------------------------------" << std::endl;
296 system_log << "lsnes shutting down at " << buffer << std::endl;
297 system_log << "-----------------------------------------------------------------------" << std::endl;
298 system_log.close();
301 std::ostream& platform::out() throw(std::bad_alloc)
303 static std::ostream* cached = NULL;
304 int dummy;
305 if(!cached)
306 cached = new boost::iostreams::stream<window_output>(&dummy);
307 return *cached;
310 messagebuffer platform::msgbuf(MAXMESSAGES, INIT_WIN_SIZE);
313 void platform::message(const std::string& msg) throw(std::bad_alloc)
315 mutex::holder h(msgbuf_lock());
316 std::string msg2 = msg;
317 while(msg2 != "") {
318 size_t s = msg2.find_first_of("\n");
319 std::string forlog;
320 if(s >= msg2.length()) {
321 msgbuf.add_message(forlog = msg2);
322 if(system_log)
323 system_log << forlog << std::endl;
324 break;
325 } else {
326 msgbuf.add_message(forlog = msg2.substr(0, s));
327 if(system_log)
328 system_log << forlog << std::endl;
329 msg2 = msg2.substr(s + 1);
334 void platform::fatal_error() throw()
336 time_t curtime = time(NULL);
337 struct tm* tm = localtime(&curtime);
338 char buffer[1024];
339 strftime(buffer, 1023, "%Y-%m-%d %H:%M:%S %Z", tm);
340 system_log << "-----------------------------------------------------------------------" << std::endl;
341 system_log << "lsnes paniced at " << buffer << std::endl;
342 system_log << "-----------------------------------------------------------------------" << std::endl;
343 system_log.close();
344 graphics_plugin::fatal_error();
345 exit(1);
348 namespace
350 mutex* queue_lock;
351 condition* queue_condition;
352 std::deque<keypress> keypresses;
353 std::deque<std::string> commands;
354 std::deque<std::pair<void(*)(void*), void*>> functions;
355 volatile bool normal_pause;
356 volatile bool modal_pause;
357 volatile uint64_t continue_time;
358 volatile uint64_t next_function;
359 volatile uint64_t functions_executed;
361 void init_threading()
363 if(!queue_lock)
364 queue_lock = &mutex::aquire();
365 if(!queue_condition)
366 queue_condition = &condition::aquire(*queue_lock);
369 void internal_run_queues(bool unlocked) throw()
371 init_threading();
372 if(!unlocked)
373 queue_lock->lock();
374 try {
375 //Flush keypresses.
376 while(!keypresses.empty()) {
377 keypress k = keypresses.front();
378 keypresses.pop_front();
379 queue_lock->unlock();
380 if(k.key1)
381 k.key1->set_position(k.value, k.modifiers);
382 if(k.key2)
383 k.key2->set_position(k.value, k.modifiers);
384 queue_lock->lock();
386 //Flush commands.
387 while(!commands.empty()) {
388 std::string c = commands.front();
389 commands.pop_front();
390 queue_lock->unlock();
391 command::invokeC(c);
392 queue_lock->lock();
394 //Flush functions.
395 while(!functions.empty()) {
396 std::pair<void(*)(void*), void*> f = functions.front();
397 functions.pop_front();
398 queue_lock->unlock();
399 f.first(f.second);
400 queue_lock->lock();
401 ++functions_executed;
403 queue_condition->signal();
404 } catch(std::bad_alloc& e) {
405 OOM_panic();
406 } catch(std::exception& e) {
407 std::cerr << "Fault inside platform::run_queues(): " << e.what() << std::endl;
408 exit(1);
410 if(!unlocked)
411 queue_lock->unlock();
415 #define MAXWAIT 10000
417 void platform::flush_command_queue() throw()
419 init_threading();
420 while(true) {
421 mutex::holder h(*queue_lock);
422 internal_run_queues(true);
423 uint64_t now = get_utime();
424 uint64_t waitleft = 0;
425 waitleft = (now < continue_time) ? (continue_time - now) : 0;
426 waitleft = (modal_pause || normal_pause) ? MAXWAIT : waitleft;
427 waitleft = (waitleft > MAXWAIT) ? MAXWAIT : waitleft;
428 if(waitleft > 0)
429 queue_condition->wait(waitleft);
430 else
431 return;
432 //If we had to wait, check queues at least once more.
436 void platform::set_paused(bool enable) throw()
438 normal_pause = enable;
441 void platform::wait(uint64_t usec) throw()
443 continue_time = get_utime() + usec;
444 init_threading();
445 while(true) {
446 mutex::holder h(*queue_lock);
447 internal_run_queues(true);
448 uint64_t now = get_utime();
449 uint64_t waitleft = 0;
450 waitleft = (now < continue_time) ? (continue_time - now) : 0;
451 waitleft = (waitleft > MAXWAIT) ? MAXWAIT : waitleft;
452 if(waitleft > 0)
453 queue_condition->wait(waitleft);
454 else
455 return;
459 void platform::cancel_wait() throw()
461 init_threading();
462 continue_time = 0;
463 mutex::holder h(*queue_lock);
464 queue_condition->signal();
467 void platform::set_modal_pause(bool enable) throw()
469 modal_pause = enable;
472 void platform::queue(const keypress& k) throw(std::bad_alloc)
474 init_threading();
475 mutex::holder h(*queue_lock);
476 keypresses.push_back(k);
477 queue_condition->signal();
480 void platform::queue(const std::string& c) throw(std::bad_alloc)
482 init_threading();
483 mutex::holder h(*queue_lock);
484 commands.push_back(c);
485 queue_condition->signal();
488 void platform::queue(void (*f)(void* arg), void* arg, bool sync) throw(std::bad_alloc)
490 if(sync && queue_synchronous_fn_warning)
491 std::cerr << "WARNING: Synchronous queue in callback to UI, this may deadlock!" << std::endl;
492 init_threading();
493 mutex::holder h(*queue_lock);
494 ++next_function;
495 functions.push_back(std::make_pair(f, arg));
496 queue_condition->signal();
497 if(sync)
498 while(functions_executed < next_function)
499 queue_condition->wait(10000);
502 void platform::run_queues() throw()
504 internal_run_queues(false);
507 namespace
509 mutex* _msgbuf_lock;
510 screen* our_screen;
512 void trigger_repaint()
514 graphics_plugin::notify_screen();
517 struct painter_listener : public information_dispatch
519 painter_listener();
520 void on_set_screen(screen& scr);
521 void on_screen_update();
522 void on_status_update();
523 } x;
525 painter_listener::painter_listener() : information_dispatch("painter-listener") {}
527 void painter_listener::on_set_screen(screen& scr)
529 our_screen = &scr;
532 void painter_listener::on_screen_update()
534 trigger_repaint();
537 void painter_listener::on_status_update()
539 graphics_plugin::notify_status();
542 struct handle_mouse_request
544 long x;
545 long y;
546 uint32_t mask;
549 void _handle_mouse(void* args)
551 struct handle_mouse_request* x = reinterpret_cast<struct handle_mouse_request*>(args);
552 information_dispatch::do_click(x->x, x->y, x->mask);
556 void send_mouse_click(long x, long y, uint32_t buttons)
558 struct handle_mouse_request z;
559 z.x = x;
560 z.y = y;
561 z.mask = buttons;
562 platform::queue(_handle_mouse, &z, true);
565 mutex& platform::msgbuf_lock() throw()
567 if(!_msgbuf_lock)
568 try {
569 _msgbuf_lock = &mutex::aquire();
570 } catch(...) {
571 OOM_panic();
573 return *_msgbuf_lock;
576 void platform::screen_set_palette(unsigned rshift, unsigned gshift, unsigned bshift) throw()
578 if(!our_screen)
579 return;
580 if(our_screen->palette_r == rshift &&
581 our_screen->palette_g == gshift &&
582 our_screen->palette_b == bshift)
583 return;
584 our_screen->set_palette(rshift, gshift, bshift);
585 trigger_repaint();
588 volatile bool queue_synchronous_fn_warning;