Skip to content

Commit

Permalink
Do not allow input queue to fill up
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
b4winckler committed Sep 12, 2008
1 parent 4100056 commit 8e7466b
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 160 deletions.
1 change: 0 additions & 1 deletion src/MacVim/MMBackend.h
Expand Up @@ -25,7 +25,6 @@
NSDictionary *colorDict;
NSDictionary *sysColorDict;
NSDictionary *actionDict;
BOOL inputReceived;
BOOL tabBarVisible;
unsigned backgroundColor;
unsigned foregroundColor;
Expand Down
293 changes: 134 additions & 159 deletions src/MacVim/MMBackend.m
Expand Up @@ -477,13 +477,6 @@ - (void)update
// 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
Expand Down Expand Up @@ -538,35 +531,35 @@ - (void)flushQueue:(BOOL)force

- (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
Expand Down Expand Up @@ -1063,138 +1056,35 @@ - (void)updateModifiedFlag

- (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
Expand Down Expand Up @@ -1309,8 +1199,6 @@ - (void)addInput:(in bycopy NSString *)input

[self addInput:input];
[self addClient:(id)client];

inputReceived = YES;
}

- (NSString *)evaluateExpression:(in bycopy NSString *)expr
Expand Down Expand Up @@ -1636,17 +1524,17 @@ - (void)processInputQueue
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;

Expand All @@ -1660,9 +1548,96 @@ - (void)processInputQueue

- (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;
Expand Down

0 comments on commit 8e7466b

Please sign in to comment.