Do not allow input queue to fill up
authorBjorn Winckler <bjorn.winckler@gmail.com>
Fri, 12 Sep 2008 22:08:47 +0000 (13 00:08 +0200)
committerBjorn Winckler <bjorn.winckler@gmail.com>
Sat, 13 Sep 2008 16:52:21 +0000 (13 18:52 +0200)
The backend keeps at most one copy of each message on the input queue.
This makes MacVim feel a lot more responsive e.g. when scrolling the
screen.  It used to be that holding down 'j' to scroll and then
releasing 'j' would cause the screen to keep scrolling for a while even
after the release.

src/MacVim/MMBackend.h
src/MacVim/MMBackend.m

index 96dafd2..f056026 100644 (file)
@@ -25,7 +25,6 @@
     NSDictionary        *colorDict;
     NSDictionary        *sysColorDict;
     NSDictionary        *actionDict;
-    BOOL                inputReceived;
     BOOL                tabBarVisible;
     unsigned            backgroundColor;
     unsigned            foregroundColor;
index 72d3dd1..2847fdc 100644 (file)
@@ -477,13 +477,6 @@ static NSString *MMSymlinkWarningString =
     // waiting.
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                              beforeDate:[NSDate distantPast]];
-
-#if 0
-    // Keyboard and mouse input is handled directly, other input is queued and
-    // processed here.  This call may enter a blocking loop.
-    if ([inputQueue count] > 0)
-        [self processInputQueue];
-#endif
 }
 
 - (void)flushQueue:(BOOL)force
@@ -538,35 +531,35 @@ static NSString *MMSymlinkWarningString =
 
 - (BOOL)waitForInput:(int)milliseconds
 {
-    //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
+    // Return NO if we timed out waiting for input, otherwise return YES.
+    BOOL inputReceived = NO;
 
     // Only start the run loop if the input queue is empty, otherwise process
     // the input first so that the input on queue isn't delayed.
     if ([inputQueue count]) {
         inputReceived = YES;
     } else {
-        NSDate *date = milliseconds > 0 ?
-                [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
-                [NSDate distantFuture];
-
-        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
-                                 beforeDate:date];
+        // Wait for the specified amount of time, unless 'milliseconds' is
+        // negative in which case we wait "forever" (1e6 seconds translates to
+        // approximately 11 days).
+        CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
+
+        while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
+                == kCFRunLoopRunHandledSource) {
+            // In order to ensure that all input on the run-loop has been
+            // processed we set the timeout to 0 and keep processing until the
+            // run-loop times out.
+            dt = 0.0;
+            inputReceived = YES;
+        }
     }
 
-    // I know of no way to figure out if the run loop exited because input was
-    // found or because of a time out, so I need to manually indicate when
-    // input was received in processInput:data: and then reset it every time
-    // here.
-    BOOL yn = inputReceived;
-    inputReceived = NO;
-
-    // Keyboard and mouse input is handled directly, other input is queued and
-    // processed here.  This call may enter a blocking loop.
+    // The above calls may have placed messages on the input queue so process
+    // it now.  This call may enter a blocking loop.
     if ([inputQueue count] > 0)
         [self processInputQueue];
 
-    //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
-    return yn;
+    return inputReceived;
 }
 
 - (void)exit
@@ -1063,138 +1056,35 @@ static NSString *MMSymlinkWarningString =
 
 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
 {
-    // NOTE: This method might get called whenever the run loop is tended to.
-    // Normal keyboard and mouse input is added to input buffers, so there is
-    // no risk in handling these events directly (they return immediately, and
-    // do not call any other Vim functions).  However, other events such
-    // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
-    // events which would cause this method to be called recursively.  This
-    // in turn leads to various difficulties that we do not want to have to
-    // deal with.  To avoid recursive calls here we add all events except
-    // keyboard and mouse events to an input queue which is processed whenever
-    // gui_mch_update() is called (see processInputQueue).
-
-    //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
-
-    // Don't flush too soon after receiving input or update speed will suffer.
-    [lastFlushDate release];
-    lastFlushDate = [[NSDate date] retain];
-
-    // Handle keyboard and mouse input now.  All other events are queued.
-    if (InsertTextMsgID == msgid) {
-        [self handleInsertText:data];
-    } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
-        if (!data) return;
-        const void *bytes = [data bytes];
-        int mods = *((int*)bytes);  bytes += sizeof(int);
-        int len = *((int*)bytes);  bytes += sizeof(int);
-        NSString *key = [[NSString alloc] initWithBytes:bytes length:len
-                                              encoding:NSUTF8StringEncoding];
-        mods = eventModifierFlagsToVimModMask(mods);
-
-        [self handleKeyDown:key modifiers:mods];
-
-        [key release];
-    } else if (ScrollWheelMsgID == msgid) {
-        if (!data) return;
-        const void *bytes = [data bytes];
-
-        int row = *((int*)bytes);  bytes += sizeof(int);
-        int col = *((int*)bytes);  bytes += sizeof(int);
-        int flags = *((int*)bytes);  bytes += sizeof(int);
-        float dy = *((float*)bytes);  bytes += sizeof(float);
-
-        int button = MOUSE_5;
-        if (dy > 0) button = MOUSE_4;
-
-        flags = eventModifierFlagsToVimMouseModMask(flags);
-
-        int numLines = (int)round(dy);
-        if (numLines < 0) numLines = -numLines;
-        if (numLines == 0) numLines = 1;
-
-#ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
-        gui.scroll_wheel_force = numLines;
-#endif
-
-        gui_send_mouse_event(button, col, row, NO, flags);
-    } else if (MouseDownMsgID == msgid) {
-        if (!data) return;
-        const void *bytes = [data bytes];
+    // Remove all previous instances of this message from the input queue, else
+    // the input queue may fill up as a result of Vim not being able to keep up
+    // with the speed at which new messages are received.  This avoids annoying
+    // situations such as when the keyboard repeat rate is higher than what Vim
+    // can cope with (which would cause a 'stutter' when scrolling by holding
+    // down 'j' and then when 'j' was released the screen kept scrolling for a
+    // little while).
 
-        int row = *((int*)bytes);  bytes += sizeof(int);
-        int col = *((int*)bytes);  bytes += sizeof(int);
-        int button = *((int*)bytes);  bytes += sizeof(int);
-        int flags = *((int*)bytes);  bytes += sizeof(int);
-        int count = *((int*)bytes);  bytes += sizeof(int);
-
-        button = eventButtonNumberToVimMouseButton(button);
-        if (button >= 0) {
-            flags = eventModifierFlagsToVimMouseModMask(flags);
-            gui_send_mouse_event(button, col, row, count>1, flags);
-        }
-    } else if (MouseUpMsgID == msgid) {
-        if (!data) return;
-        const void *bytes = [data bytes];
-
-        int row = *((int*)bytes);  bytes += sizeof(int);
-        int col = *((int*)bytes);  bytes += sizeof(int);
-        int flags = *((int*)bytes);  bytes += sizeof(int);
-
-        flags = eventModifierFlagsToVimMouseModMask(flags);
-
-        gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
-    } else if (MouseDraggedMsgID == msgid) {
-        if (!data) return;
-        const void *bytes = [data bytes];
-
-        int row = *((int*)bytes);  bytes += sizeof(int);
-        int col = *((int*)bytes);  bytes += sizeof(int);
-        int flags = *((int*)bytes);  bytes += sizeof(int);
-
-        flags = eventModifierFlagsToVimMouseModMask(flags);
-
-        gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
-    } else if (MouseMovedMsgID == msgid) {
-        const void *bytes = [data bytes];
-        int row = *((int*)bytes);  bytes += sizeof(int);
-        int col = *((int*)bytes);  bytes += sizeof(int);
-
-        gui_mouse_moved(col, row);
-    } else if (AddInputMsgID == msgid) {
-        NSString *string = [[NSString alloc] initWithData:data
-                encoding:NSUTF8StringEncoding];
-        if (string) {
-            [self addInput:string];
-            [string release];
+    int i, count = [inputQueue count];
+    for (i = 1; i < count; i+=2) {
+        if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
+            [inputQueue removeObjectAtIndex:i];
+            [inputQueue removeObjectAtIndex:i-1];
+            break;
         }
-    } else if (TerminateNowMsgID == msgid) {
-        isTerminating = YES;
-    } else {
-        // Not keyboard or mouse event, queue it and handle later.
-        //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
-        [inputQueue addObject:[NSNumber numberWithInt:msgid]];
-        [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
     }
 
-    // See waitForInput: for an explanation of this flag.
-    inputReceived = YES;
+    [inputQueue addObject:[NSNumber numberWithInt:msgid]];
+    [inputQueue addObject:(data ? (id)data : [NSNull null])];
 }
 
 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
 {
-    // TODO: Get rid of this method?
-    //NSLog(@"%s%@", _cmd, messages);
-
-    unsigned i, count = [messages count];
-    for (i = 0; i < count; i += 2) {
-        int msgid = [[messages objectAtIndex:i] intValue];
-        id data = [messages objectAtIndex:i+1];
-        if ([data isEqual:[NSNull null]])
-            data = nil;
-
-        [self processInput:msgid data:data];
-    }
+    // This is just a convenience method that allows the frontend to delay
+    // sending messages.
+    int i, count = [messages count];
+    for (i = 1; i < count; i+=2)
+        [self processInput:[[messages objectAtIndex:i-1] intValue]
+                      data:[messages objectAtIndex:i]];
 }
 
 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
@@ -1309,8 +1199,6 @@ static NSString *MMSymlinkWarningString =
 
     [self addInput:input];
     [self addClient:(id)client];
-
-    inputReceived = YES;
 }
 
 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
@@ -1636,17 +1524,17 @@ static NSString *MMSymlinkWarningString =
     if ([inputQueue count] == 0) return;
 
     // NOTE: One of the input events may cause this method to be called
-    // recursively, so copy the input queue to a local variable and clear it
-    // before starting to process input events (otherwise we could get stuck in
-    // an endless loop).
+    // recursively, so copy the input queue to a local variable and clear the
+    // queue before starting to process input events (otherwise we could get
+    // stuck in an endless loop).
     NSArray *q = [inputQueue copy];
     unsigned i, count = [q count];
 
     [inputQueue removeAllObjects];
 
-    for (i = 0; i < count-1; i += 2) {
-        int msgid = [[q objectAtIndex:i] intValue];
-        id data = [q objectAtIndex:i+1];
+    for (i = 1; i < count; i+=2) {
+        int msgid = [[q objectAtIndex:i-1] intValue];
+        id data = [q objectAtIndex:i];
         if ([data isEqual:[NSNull null]])
             data = nil;
 
@@ -1660,9 +1548,96 @@ static NSString *MMSymlinkWarningString =
 
 - (void)handleInputEvent:(int)msgid data:(NSData *)data
 {
-    //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
+    if (InsertTextMsgID == msgid) {
+        [self handleInsertText:data];
+    } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
+        if (!data) return;
+        const void *bytes = [data bytes];
+        int mods = *((int*)bytes);  bytes += sizeof(int);
+        int len = *((int*)bytes);  bytes += sizeof(int);
+        NSString *key = [[NSString alloc] initWithBytes:bytes length:len
+                                              encoding:NSUTF8StringEncoding];
+        mods = eventModifierFlagsToVimModMask(mods);
+
+        [self handleKeyDown:key modifiers:mods];
+
+        [key release];
+    } else if (ScrollWheelMsgID == msgid) {
+        if (!data) return;
+        const void *bytes = [data bytes];
+
+        int row = *((int*)bytes);  bytes += sizeof(int);
+        int col = *((int*)bytes);  bytes += sizeof(int);
+        int flags = *((int*)bytes);  bytes += sizeof(int);
+        float dy = *((float*)bytes);  bytes += sizeof(float);
+
+        int button = MOUSE_5;
+        if (dy > 0) button = MOUSE_4;
+
+        flags = eventModifierFlagsToVimMouseModMask(flags);
+
+        int numLines = (int)round(dy);
+        if (numLines < 0) numLines = -numLines;
+        if (numLines == 0) numLines = 1;
+
+#ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
+        gui.scroll_wheel_force = numLines;
+#endif
+
+        gui_send_mouse_event(button, col, row, NO, flags);
+    } else if (MouseDownMsgID == msgid) {
+        if (!data) return;
+        const void *bytes = [data bytes];
+
+        int row = *((int*)bytes);  bytes += sizeof(int);
+        int col = *((int*)bytes);  bytes += sizeof(int);
+        int button = *((int*)bytes);  bytes += sizeof(int);
+        int flags = *((int*)bytes);  bytes += sizeof(int);
+        int count = *((int*)bytes);  bytes += sizeof(int);
 
-    if (SelectTabMsgID == msgid) {
+        button = eventButtonNumberToVimMouseButton(button);
+        if (button >= 0) {
+            flags = eventModifierFlagsToVimMouseModMask(flags);
+            gui_send_mouse_event(button, col, row, count>1, flags);
+        }
+    } else if (MouseUpMsgID == msgid) {
+        if (!data) return;
+        const void *bytes = [data bytes];
+
+        int row = *((int*)bytes);  bytes += sizeof(int);
+        int col = *((int*)bytes);  bytes += sizeof(int);
+        int flags = *((int*)bytes);  bytes += sizeof(int);
+
+        flags = eventModifierFlagsToVimMouseModMask(flags);
+
+        gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
+    } else if (MouseDraggedMsgID == msgid) {
+        if (!data) return;
+        const void *bytes = [data bytes];
+
+        int row = *((int*)bytes);  bytes += sizeof(int);
+        int col = *((int*)bytes);  bytes += sizeof(int);
+        int flags = *((int*)bytes);  bytes += sizeof(int);
+
+        flags = eventModifierFlagsToVimMouseModMask(flags);
+
+        gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
+    } else if (MouseMovedMsgID == msgid) {
+        const void *bytes = [data bytes];
+        int row = *((int*)bytes);  bytes += sizeof(int);
+        int col = *((int*)bytes);  bytes += sizeof(int);
+
+        gui_mouse_moved(col, row);
+    } else if (AddInputMsgID == msgid) {
+        NSString *string = [[NSString alloc] initWithData:data
+                encoding:NSUTF8StringEncoding];
+        if (string) {
+            [self addInput:string];
+            [string release];
+        }
+    } else if (TerminateNowMsgID == msgid) {
+        isTerminating = YES;
+    } else if (SelectTabMsgID == msgid) {
         if (!data) return;
         const void *bytes = [data bytes];
         int idx = *((int*)bytes) + 1;