From ddee65158cd55a0c045a1ebc26f8ff73768cdc64 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jan=20Dj=C3=A4rv?= Date: Wed, 15 Aug 2012 20:58:19 +0200 Subject: [PATCH] Improve event loop on NS so that no polling is used. * nsmenu.m (popupSession): Remove. (pop_down_menu): Remove endModalSession. (timeout_handler:): New method. (runDialogAt:): Get next timeout. Start a NSTimer with that timeout. Call runModalForWindow. Check timer_fired when it returns. If not set, cancel timer and break out of loop. Otherwise loop again, with a new timeout. * nsterm.h (EmacsApp): fd_handler takes id argument. (EmacsDialogPanel): Add timer_fired and timeout_handler. * nsterm.m: Include fcntl.h if present. (fd_entry, t_readfds, inNsSelect): Remove. (select_writefds, select_valid, select_timeout, selfds) (select_mutex, apploopnr): Add. (EV_TRAILER): Call kbd_buffer_store_event_hold only if q_event_ptr. Otherwise call kbd_buffer_store_event. (ns_send_appdefined): Remove release of fd_entry. (ns_read_socket): Always send appdefined. Remove inNsSelect check. Increment and decrement apploopnr. (ns_select): If no file descriptors, just do a NSTimer. Otherwise copy read/write masks and start select thread (fd_handler). Start main loop and wait for application defined event. Inform select thread to stop selecting after main loop is exited. (ns_term_init): Create selfds pipe and set non-blocking. Initialize select_mutex. Start the select thread (fd_handler). (fd_handler:): Loop forever, wait for info from the main thread to either start or stop selecting. When select returns, send and appdefined event. (sendScrollEventAtLoc:fromEvent:): Check if q_event_ptr is set. If not call kbd_buffer_store_event. --- src/ChangeLog | 32 +++++ src/nsmenu.m | 43 +++++-- src/nsterm.h | 4 +- src/nsterm.m | 322 +++++++++++++++++++++++++++++++++----------------- 4 files changed, 281 insertions(+), 120 deletions(-) diff --git a/src/ChangeLog b/src/ChangeLog index 04a0df774f..6e49dd44fd 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,5 +1,37 @@ 2012-08-15 Jan Djärv + * nsmenu.m (popupSession): Remove. + (pop_down_menu): Remove endModalSession. + (timeout_handler:): New method. + (runDialogAt:): Get next timeout. Start a NSTimer with that timeout. + Call runModalForWindow. Check timer_fired when it returns. + If not set, cancel timer and break out of loop. + Otherwise loop again, with a new timeout. + + * nsterm.m: Include fcntl.h if present. + (fd_entry, t_readfds, inNsSelect): Remove. + (select_writefds, select_valid, select_timeout, selfds) + (select_mutex, apploopnr): Add. + (EV_TRAILER): Call kbd_buffer_store_event_hold only if q_event_ptr. + Otherwise call kbd_buffer_store_event. + (ns_send_appdefined): Remove release of fd_entry. + (ns_read_socket): Always send appdefined. Remove inNsSelect check. + Increment and decrement apploopnr. + (ns_select): If no file descriptors, just do a NSTimer. + Otherwise copy read/write masks and start select thread (fd_handler). + Start main loop and wait for application defined event. + Inform select thread to stop selecting after main loop is exited. + (ns_term_init): Create selfds pipe and set non-blocking. + Initialize select_mutex. Start the select thread (fd_handler). + (fd_handler:): Loop forever, wait for info from the main thread + to either start or stop selecting. When select returns, send + and appdefined event. + (sendScrollEventAtLoc:fromEvent:): Check if q_event_ptr is set. + If not call kbd_buffer_store_event. + + * nsterm.h (EmacsApp): fd_handler takes id argument. + (EmacsDialogPanel): Add timer_fired and timeout_handler. + * gtkutil.c (xg_mark_data): Use FRAME_X_P. 2012-08-15 Eli Zaretskii diff --git a/src/nsmenu.m b/src/nsmenu.m index 210f4530d7..657b930694 100644 --- a/src/nsmenu.m +++ b/src/nsmenu.m @@ -73,7 +73,6 @@ EmacsMenu *mainMenu, *svcsMenu, *dockMenu; /* Nonzero means a menu is currently active. */ static int popup_activated_flag; -static NSModalSession popupSession; /* Nonzero means we are tracking and updating menus. */ static int trackingMenu; @@ -1365,8 +1364,6 @@ pop_down_menu (Lisp_Object arg) { EmacsDialogPanel *panel = unwind_data->dialog; popup_activated_flag = 0; - [NSApp endModalSession: popupSession]; - [panel close]; [unwind_data->pool release]; [[FRAME_NS_VIEW (SELECTED_FRAME ()) window] makeKeyWindow]; @@ -1756,20 +1753,40 @@ void process_dialog (id window, Lisp_Object list) } + +- (void)timeout_handler: (NSTimer *)timedEntry +{ + timer_fired = 1; + [NSApp abortModal]; +} + - (Lisp_Object)runDialogAt: (NSPoint)p { - NSInteger ret; + NSInteger ret = 0; - /* initiate a session that will be ended by pop_down_menu */ - popupSession = [NSApp beginModalSessionForWindow: self]; - while (popup_activated_flag - && (ret = [NSApp runModalSession: popupSession]) - == NSRunContinuesResponse) + while (popup_activated_flag) { - /* Run this for timers.el, indep of atimers; might not return. - TODO: use return value to avoid calling every iteration. */ - timer_check (); - [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]]; + NSTimer *tmo = nil; + EMACS_TIME next_time = timer_check (); + + if (EMACS_TIME_VALID_P (next_time)) + { + double time = EMACS_TIME_TO_DOUBLE (next_time); + tmo = [NSTimer timerWithTimeInterval: time + target: self + selector: @selector (timeout_handler:) + userInfo: 0 + repeats: NO]; + [[NSRunLoop currentRunLoop] addTimer: tmo + forMode: NSModalPanelRunLoopMode]; + } + timer_fired = 0; + ret = [NSApp runModalForWindow: self]; + if (! timer_fired) + { + if (tmo != nil) [tmo invalidate]; /* Cancels timer */ + break; + } } { /* FIXME: BIG UGLY HACK!!! */ diff --git a/src/nsterm.h b/src/nsterm.h index de41929e3f..94984b3d35 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -56,7 +56,7 @@ along with GNU Emacs. If not, see . */ - (void)sendEvent: (NSEvent *)theEvent; - (void)showPreferencesWindow: (id)sender; - (BOOL) openFile: (NSString *)fileName; -- (void)fd_handler: (NSTimer *) fdEntry; +- (void)fd_handler: (id)unused; - (void)timeout_handler: (NSTimer *)timedEntry; - (BOOL)fulfillService: (NSString *)name withArg: (NSString *)arg; @end @@ -195,12 +195,14 @@ along with GNU Emacs. If not, see . */ NSTextField *title; NSMatrix *matrix; int rows, cols; + int timer_fired; } - initFromContents: (Lisp_Object)menu isQuestion: (BOOL)isQ; - addButton: (char *)str value: (Lisp_Object)val row: (int)row; - addString: (char *)str row: (int)row; - addSplit; - (Lisp_Object)runDialogAt: (NSPoint)p; +- (void)timeout_handler: (NSTimer *)timedEntry; @end #if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 diff --git a/src/nsterm.m b/src/nsterm.m index 5e3c3ac777..76e6ee8fb4 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -39,6 +39,10 @@ GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu) #include #include +#ifdef HAVE_FCNTL_H +#include +#endif + #include "lisp.h" #include "blockinput.h" #include "sysselect.h" @@ -184,17 +188,20 @@ static BOOL ns_menu_bar_is_hidden = NO; static BOOL send_appdefined = YES; static NSEvent *last_appdefined_event = 0; static NSTimer *timed_entry = 0; -static NSTimer *fd_entry = nil; static NSTimer *scroll_repeat_entry = nil; -static fd_set select_readfds, t_readfds; -static int select_nfds; +static fd_set select_readfds, select_writefds; +enum { SELECT_HAVE_READ = 1, SELECT_HAVE_WRITE = 2, SELECT_HAVE_TMO = 4 }; +static int select_nfds = 0, select_valid = 0; +static EMACS_TIME select_timeout = { 0, 0 }; +static int selfds[2] = { -1, -1 }; +static pthread_mutex_t select_mutex; +static int apploopnr = 0; static NSAutoreleasePool *outerpool; static struct input_event *emacs_event = NULL; static struct input_event *q_event_ptr = NULL; static int n_emacs_events_pending = 0; static NSMutableArray *ns_pending_files, *ns_pending_service_names, *ns_pending_service_args; -static BOOL inNsSelect = 0; static BOOL ns_do_open_file = NO; /* Convert modifiers in a NeXTstep event to emacs style modifiers. */ @@ -252,15 +259,20 @@ static BOOL ns_do_open_file = NO; /* This is a piece of code which is common to all the event handling methods. Maybe it should even be a function. */ -#define EV_TRAILER(e) \ - { \ - XSETFRAME (emacs_event->frame_or_window, emacsframe); \ - if (e) emacs_event->timestamp = EV_TIMESTAMP (e); \ - n_emacs_events_pending++; \ - kbd_buffer_store_event_hold (emacs_event, q_event_ptr); \ - EVENT_INIT (*emacs_event); \ - ns_send_appdefined (-1); \ - } +#define EV_TRAILER(e) \ + { \ + XSETFRAME (emacs_event->frame_or_window, emacsframe); \ + if (e) emacs_event->timestamp = EV_TIMESTAMP (e); \ + if (q_event_ptr) \ + { \ + n_emacs_events_pending++; \ + kbd_buffer_store_event_hold (emacs_event, q_event_ptr); \ + } \ + else \ + kbd_buffer_store_event (emacs_event); \ + EVENT_INIT (*emacs_event); \ + ns_send_appdefined (-1); \ + } void x_set_cursor_type (struct frame *, Lisp_Object, Lisp_Object); @@ -3377,14 +3389,6 @@ ns_send_appdefined (int value) timed_entry = nil; } - /* Ditto for file descriptor poller */ - if (fd_entry) - { - [fd_entry invalidate]; - [fd_entry release]; - fd_entry = nil; - } - nxev = [NSEvent otherEventWithType: NSApplicationDefined location: NSMakePoint (0, 0) modifierFlags: 0 @@ -3402,7 +3406,6 @@ ns_send_appdefined (int value) } } - static int ns_read_socket (struct terminal *terminal, int expected, struct input_event *hold_quit) @@ -3466,24 +3469,14 @@ ns_read_socket (struct terminal *terminal, int expected, /* Run and wait for events. We must always send one NX_APPDEFINED event to ourself, otherwise [NXApp run] will never exit. */ send_appdefined = YES; + ns_send_appdefined (-1); - /* If called via ns_select, this is called once with expected=1, - because we expect either the timeout or file descriptor activity. - In this case the first event through will either be real input or - one of these. read_avail_input() then calls once more with expected=0 - and in that case we need to return quickly if there is nothing. - If we're being called outside of that, it's also OK to return quickly - after one iteration through the event loop, since other terms do - this and emacs expects it. */ - if (!(inNsSelect && expected)) + if (++apploopnr != 1) { - /* Post an application defined event on the event queue. When this is - received the [NXApp run] will return, thus having processed all - events which are currently queued, if any. */ - ns_send_appdefined (-1); + abort (); } - [NSApp run]; + --apploopnr; } nevents = n_emacs_events_pending; @@ -3503,65 +3496,89 @@ ns_select (int nfds, fd_set *readfds, fd_set *writefds, -------------------------------------------------------------------------- */ { int result; - double time; NSEvent *ev; - struct timespec select_timeout; + int k, nr = 0; + struct input_event event; + char c; /* NSTRACE (ns_select); */ - if (NSApp == nil || inNsSelect == 1 /* || ([NSApp isActive] == NO && - [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil - inMode:NSDefaultRunLoopMode dequeue:NO] == nil) */) + for (k = 0; readfds && k < nfds+1; k++) + if (FD_ISSET(k, readfds)) ++nr; + + if (NSApp == nil + || (timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0)) return pselect (nfds, readfds, writefds, exceptfds, timeout, sigmask); - /* Save file descriptor set, which gets overwritten in calls to select () - Note, this is called from process.c, and only readfds is ever set */ - if (readfds) + [outerpool release]; + outerpool = [[NSAutoreleasePool alloc] init]; + + + send_appdefined = YES; + if (nr > 0) { - memcpy (&select_readfds, readfds, sizeof (fd_set)); + pthread_mutex_lock (&select_mutex); select_nfds = nfds; + select_valid = 0; + if (readfds) + { + select_readfds = *readfds; + select_valid += SELECT_HAVE_READ; + } + if (writefds) + { + select_writefds = *writefds; + select_valid += SELECT_HAVE_WRITE; + } + + if (timeout) + { + select_timeout = *timeout; + select_valid += SELECT_HAVE_TMO; + } + + pthread_mutex_unlock (&select_mutex); + + /* Inform fd_handler that select should be called */ + c = 'g'; + write (selfds[1], &c, 1); } - else - select_nfds = 0; - - /* Try an initial select for pending data on input files */ - select_timeout.tv_sec = select_timeout.tv_nsec = 0; - result = pselect (nfds, readfds, writefds, exceptfds, - &select_timeout, sigmask); - if (result) - return result; - - /* if (!timeout || timed_entry || fd_entry) - fprintf (stderr, "assertion failed: timeout null or timed_entry/fd_entry non-null in ns_select\n"); */ - - /* set a timeout and run the main AppKit event loop while continuing - to monitor the files */ - time = EMACS_TIME_TO_DOUBLE (*timeout); - timed_entry = [[NSTimer scheduledTimerWithTimeInterval: time - target: NSApp - selector: @selector (timeout_handler:) - userInfo: 0 - repeats: YES] /* for safe removal */ - retain]; - - /* set a periodic task to try the pselect () again */ - fd_entry = [[NSTimer scheduledTimerWithTimeInterval: 0.1 - target: NSApp - selector: @selector (fd_handler:) - userInfo: 0 - repeats: YES] - retain]; - - /* Let Application dispatch events until it receives an event of the type - NX_APPDEFINED, which should only be sent by timeout_handler. - We tell read_avail_input() that input is "expected" because we do expect - either the timeout or fd handler to fire, and if they don't, the original - call from process.c that got us here expects us to wait until some input - comes. */ - inNsSelect = 1; - gobble_input (1); + else if (nr == 0 && timeout) + { + /* No file descriptor, just a timeout, no need to wake fd_handler */ + double time = EMACS_TIME_TO_DOUBLE (*timeout); + timed_entry = [[NSTimer scheduledTimerWithTimeInterval: time + target: NSApp + selector: + @selector (timeout_handler:) + userInfo: 0 + repeats: NO] + retain]; + } + else /* No timeout and no file descriptors, can this happen? */ + { + /* Send appdefined so we exit from the loop */ + ns_send_appdefined (-1); + } + + EVENT_INIT (event); + BLOCK_INPUT; + emacs_event = &event; + if (++apploopnr != 1) + { + abort(); + } + [NSApp run]; + --apploopnr; + emacs_event = NULL; + if (nr > 0 && readfds) + { + c = 's'; + write (selfds[1], &c, 1); + } + UNBLOCK_INPUT; + ev = last_appdefined_event; - inNsSelect = 0; if (ev) { @@ -3575,25 +3592,28 @@ ns_select (int nfds, fd_set *readfds, fd_set *writefds, if (t == -2) { /* The NX_APPDEFINED event we received was a timeout. */ - return 0; + result = 0; } else if (t == -1) { /* The NX_APPDEFINED event we received was the result of at least one real input event arriving. */ errno = EINTR; - return -1; + result = -1; } else { - /* Received back from pselect () in fd_handler; copy the results */ - if (readfds) - memcpy (readfds, &select_readfds, sizeof (fd_set)); - return t; + /* Received back from select () in fd_handler; copy the results */ + pthread_mutex_lock (&select_mutex); + if (readfds) *readfds = select_readfds; + if (writefds) *writefds = select_writefds; + if (timeout) *timeout = select_timeout; + pthread_mutex_unlock (&select_mutex); + result = t; } } - /* never reached, shut compiler up */ - return 0; + + return result; } @@ -4024,6 +4044,21 @@ ns_term_init (Lisp_Object display_name) { baud_rate = 38400; Fset_input_interrupt_mode (Qnil); + + if (selfds[0] == -1) + { + if (pipe (selfds) == -1) + { + fprintf (stderr, "Failed to create pipe: %s\n", + emacs_strerror (errno)); + abort (); + } + + fcntl (selfds[0], F_SETFL, O_NONBLOCK|fcntl (selfds[0], F_GETFL)); + FD_ZERO (&select_readfds); + FD_ZERO (&select_writefds); + pthread_mutex_init (&select_mutex, NULL); + } ns_initialized = 1; } @@ -4039,6 +4074,11 @@ ns_term_init (Lisp_Object display_name) return NULL; [NSApp setDelegate: NSApp]; + /* Start the select thread. */ + [NSThread detachNewThreadSelector:@selector (fd_handler:) + toTarget:NSApp + withObject:nil]; + /* debugging: log all notifications */ /* [[NSNotificationCenter defaultCenter] addObserver: NSApp selector: @selector (logNotification:) @@ -4547,26 +4587,91 @@ not_in_argv (NSString *arg) ns_send_appdefined (-2); } -- (void)fd_handler: (NSTimer *) fdEntry +- (void)fd_handler:(id)unused /* -------------------------------------------------------------------------- Check data waiting on file descriptors and terminate if so -------------------------------------------------------------------------- */ { int result; - struct timespec select_timeout; - /* NSTRACE (fd_handler); */ + int waiting = 1, nfds; + char c; - if (select_nfds == 0) - return; + SELECT_TYPE readfds, writefds, *wfds; + EMACS_TIME timeout, *tmo; - memcpy (&t_readfds, &select_readfds, sizeof (fd_set)); + /* NSTRACE (fd_handler); */ - select_timeout.tv_sec = select_timeout.tv_nsec = 0; - result = pselect (select_nfds, &t_readfds, NULL, NULL, &select_timeout, NULL); - if (result) + for (;;) { - memcpy (&select_readfds, &t_readfds, sizeof (fd_set)); - ns_send_appdefined (result); + if (waiting) + { + SELECT_TYPE fds; + + FD_SET (selfds[0], &fds); + result = select (selfds[0]+1, &fds, NULL, NULL, NULL); + if (result > 0) + { + read (selfds[0], &c, 1); + if (c == 'g') waiting = 0; + } + } + else + { + pthread_mutex_lock (&select_mutex); + nfds = select_nfds; + + if (select_valid & SELECT_HAVE_READ) + readfds = select_readfds; + else + FD_ZERO (&readfds); + + if (select_valid & SELECT_HAVE_WRITE) + { + writefds = select_writefds; + wfds = &writefds; + } + else + wfds = NULL; + if (select_valid & SELECT_HAVE_TMO) + { + timeout = select_timeout; + tmo = &timeout; + } + else + tmo = NULL; + + pthread_mutex_unlock (&select_mutex); + + FD_SET (selfds[0], &readfds); + if (selfds[0] >= nfds) nfds = selfds[0]+1; + + result = pselect (nfds, &readfds, wfds, NULL, tmo, NULL); + + if (result == 0) + ns_send_appdefined (-2); + else if (result > 0) + { + if (FD_ISSET (selfds[0], &readfds)) + { + read (selfds[0], &c, 1); + if (c == 's') waiting = 1; + } + else + { + pthread_mutex_lock (&select_mutex); + if (select_valid & SELECT_HAVE_READ) + select_readfds = readfds; + if (select_valid & SELECT_HAVE_WRITE) + select_writefds = writefds; + if (select_valid & SELECT_HAVE_TMO) + select_timeout = timeout; + pthread_mutex_unlock (&select_mutex); + + ns_send_appdefined (result); + } + } + waiting = 1; + } } } @@ -6404,8 +6509,13 @@ not_in_argv (NSString *arg) XSETINT (emacs_event->x, loc * pixel_height); XSETINT (emacs_event->y, pixel_height-20); - n_emacs_events_pending++; - kbd_buffer_store_event_hold (emacs_event, q_event_ptr); + if (q_event_ptr) + { + n_emacs_events_pending++; + kbd_buffer_store_event_hold (emacs_event, q_event_ptr); + } + else + kbd_buffer_store_event (emacs_event); EVENT_INIT (*emacs_event); ns_send_appdefined (-1); } -- 2.20.1