Skip to content
Snippets Groups Projects
Commit 9077b924 authored by George Nachman's avatar George Nachman
Browse files

Move cadence management out of PTYSession and into its own class.

Refactor timer stuff into a single file.

Defer decreasing the frame rate until the next frame is drawn to try to avoid issues like 5928.
parent 40ee7b46
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -1380,6 +1380,10 @@
A65B72B41B33C07D00F947A7 /* NextTip@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A65B72B01B33C07D00F947A7 /* NextTip@2x.png */; };
A65B72B51B33C07D00F947A7 /* NextTip@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A65B72B01B33C07D00F947A7 /* NextTip@2x.png */; };
A65B72B61B33C07D00F947A7 /* NextTip@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A65B72B01B33C07D00F947A7 /* NextTip@2x.png */; };
A65EC02F1F31800300AC0A6B /* iTermUpdateCadenceController.h in Headers */ = {isa = PBXBuildFile; fileRef = A65EC02D1F31800300AC0A6B /* iTermUpdateCadenceController.h */; };
A65EC0301F31800300AC0A6B /* iTermUpdateCadenceController.m in Sources */ = {isa = PBXBuildFile; fileRef = A65EC02E1F31800300AC0A6B /* iTermUpdateCadenceController.m */; };
A65EC0331F3181E700AC0A6B /* NSTimer+iTerm.h in Headers */ = {isa = PBXBuildFile; fileRef = A65EC0311F3181E700AC0A6B /* NSTimer+iTerm.h */; };
A65EC0341F3181E700AC0A6B /* NSTimer+iTerm.m in Sources */ = {isa = PBXBuildFile; fileRef = A65EC0321F3181E700AC0A6B /* NSTimer+iTerm.m */; };
A663010819CFCE74004AF81C /* SuppressAllOutput.png in Resources */ = {isa = PBXBuildFile; fileRef = A663010619CFCE74004AF81C /* SuppressAllOutput.png */; };
A663010919CFCE74004AF81C /* SuppressAllOutput.png in Resources */ = {isa = PBXBuildFile; fileRef = A663010619CFCE74004AF81C /* SuppressAllOutput.png */; };
A663010A19CFCE74004AF81C /* SuppressAllOutput.png in Resources */ = {isa = PBXBuildFile; fileRef = A663010619CFCE74004AF81C /* SuppressAllOutput.png */; };
Loading
Loading
@@ -3353,6 +3357,10 @@
A65B72A81B3285A300F947A7 /* DisableTips@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "DisableTips@2x.png"; path = "images/DisableTips@2x.png"; sourceTree = "<group>"; };
A65B72AF1B33C07D00F947A7 /* NextTip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NextTip.png; path = images/NextTip.png; sourceTree = "<group>"; };
A65B72B01B33C07D00F947A7 /* NextTip@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "NextTip@2x.png"; path = "images/NextTip@2x.png"; sourceTree = "<group>"; };
A65EC02D1F31800300AC0A6B /* iTermUpdateCadenceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermUpdateCadenceController.h; sourceTree = "<group>"; };
A65EC02E1F31800300AC0A6B /* iTermUpdateCadenceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermUpdateCadenceController.m; sourceTree = "<group>"; };
A65EC0311F3181E700AC0A6B /* NSTimer+iTerm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+iTerm.h"; sourceTree = "<group>"; };
A65EC0321F3181E700AC0A6B /* NSTimer+iTerm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+iTerm.m"; sourceTree = "<group>"; };
A663010619CFCE74004AF81C /* SuppressAllOutput.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = SuppressAllOutput.png; path = images/SuppressAllOutput.png; sourceTree = "<group>"; };
A663010719CFCE74004AF81C /* SuppressAllOutput@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "SuppressAllOutput@2x.png"; path = "images/SuppressAllOutput@2x.png"; sourceTree = "<group>"; };
A663010E19CFE41A004AF81C /* iTermAboutWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermAboutWindow.h; sourceTree = "<group>"; };
Loading
Loading
@@ -5719,6 +5727,10 @@
A60BB38D1EB6A08A00D76C09 /* iTermProcessCollection.m */,
A66EF82A1EF59CFC0005891A /* iTermRateLimitedUpdate.h */,
A66EF82B1EF59CFC0005891A /* iTermRateLimitedUpdate.m */,
A65EC02D1F31800300AC0A6B /* iTermUpdateCadenceController.h */,
A65EC02E1F31800300AC0A6B /* iTermUpdateCadenceController.m */,
A65EC0311F3181E700AC0A6B /* NSTimer+iTerm.h */,
A65EC0321F3181E700AC0A6B /* NSTimer+iTerm.m */,
);
name = Helpers;
sourceTree = "<group>";
Loading
Loading
@@ -6810,6 +6822,7 @@
A667190A1DCE36C3000CE608 /* iTermRootTerminalView.h in Headers */,
A667190B1DCE36C3000CE608 /* iTermCarbonHotKeyController.h in Headers */,
A667190C1DCE36C3000CE608 /* iTermWeakReference.h in Headers */,
A65EC0331F3181E700AC0A6B /* NSTimer+iTerm.h in Headers */,
A667190D1DCE36C3000CE608 /* iTermHostRecordMO+CoreDataProperties.h in Headers */,
A62A1F711E6E724B00363EE9 /* iTermMenuOpener.h in Headers */,
A667190E1DCE36C3000CE608 /* iTermMark.h in Headers */,
Loading
Loading
@@ -6879,6 +6892,7 @@
A66719481DCE36C3000CE608 /* iTermAdditionalHotKeyTableCellView.h in Headers */,
A66719491DCE36C3000CE608 /* iTermHotkeyPreferencesWindowController.h in Headers */,
A667194A1DCE36C3000CE608 /* iTermSessionHotkeyController.h in Headers */,
A65EC02F1F31800300AC0A6B /* iTermUpdateCadenceController.h in Headers */,
A667194B1DCE36C3000CE608 /* iTermModifierRemapper.h in Headers */,
A667194C1DCE36C3000CE608 /* iTermSocket.h in Headers */,
A667194D1DCE36C3000CE608 /* iTermWebSocketConnection.h in Headers */,
Loading
Loading
@@ -8397,7 +8411,9 @@
A66719641DCE3772000CE608 /* iTermSocket.m in Sources */,
A62A1F771E711BC000363EE9 /* iTermHelpMessageViewController.m in Sources */,
A62A1F721E6E724B00363EE9 /* iTermMenuOpener.m in Sources */,
A65EC0301F31800300AC0A6B /* iTermUpdateCadenceController.m in Sources */,
A66719661DCE3772000CE608 /* iTermWebSocketFrame.m in Sources */,
A65EC0341F3181E700AC0A6B /* NSTimer+iTerm.m in Sources */,
A66EF82D1EF59CFC0005891A /* iTermRateLimitedUpdate.m in Sources */,
A66719601DCE3772000CE608 /* iTermAPIServer.m in Sources */,
A60BB38F1EB6A08A00D76C09 /* iTermProcessCollection.m in Sources */,
//
// NSTimer+iTerm.h
// iTerm2
//
// Created by George Nachman on 8/1/17.
//
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSTimer (iTerm)
// Careful, this one isn't scheduled. You have to add it to the runloop yourself.
+ (instancetype)weakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector
userInfo:(nullable id)userInfo
repeats:(BOOL)repeats;
// Like the similarly named NSTimer method but does not retain aTarget.
+ (instancetype)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
// Block based API since the OS's isn't available until 10.12
+ (instancetype)it_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
repeats:(BOOL)repeats
block:(void (^_Nonnull)(NSTimer * _Nonnull timer))block;
@end
NS_ASSUME_NONNULL_END
//
// NSTimer+iTerm.m
// iTerm2
//
// Created by George Nachman on 8/1/17.
//
//
#import "NSTimer+iTerm.h"
@interface iTermTimerProxy : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic) SEL selector;
- (void)performBlock:(NSTimer *)timer;
@end
@implementation iTermTimerProxy
- (void)timerDidFire:(NSTimer *)timer {
id target = self.target;
if (target) {
((void (*)(id, SEL, NSTimer *))[target methodForSelector:self.selector])(self.target, self.selector, timer);
}
}
- (void)performBlock:(NSTimer *)timer {
void (^block)(NSTimer * _Nonnull) = timer.userInfo;
if (block != nil) {
block(timer);
}
}
@end
@implementation NSTimer (iTerm)
+ (instancetype)weakTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
iTermTimerProxy *proxy = [[iTermTimerProxy alloc] init];
proxy.target = target;
proxy.selector = selector;
return [NSTimer timerWithTimeInterval:interval
target:proxy
selector:@selector(timerDidFire:)
userInfo:userInfo
repeats:repeats];
}
+ (instancetype)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
iTermTimerProxy *proxy = [[iTermTimerProxy alloc] init];
proxy.target = aTarget;
proxy.selector = aSelector;
return [NSTimer scheduledTimerWithTimeInterval:ti
target:proxy
selector:@selector(timerDidFire:)
userInfo:userInfo
repeats:yesOrNo];
}
+ (instancetype)it_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
repeats:(BOOL)repeats
block:(void (^_Nonnull)(NSTimer * _Nonnull timer))block {
iTermTimerProxy *proxy = [[iTermTimerProxy alloc] init];
return [NSTimer scheduledTimerWithTimeInterval:timeInterval
target:proxy
selector:@selector(performBlock:)
userInfo:[block copy]
repeats:repeats];
}
- (void)it_performSelector:(SEL)selector onTarget:(id)target {
if (target) {
void (*func)(id, SEL, NSTimer *) = (void *)[target methodForSelector:selector];
func(target, selector, self);
}
}
@end
Loading
Loading
@@ -42,6 +42,7 @@
#import "iTermSystemVersion.h"
#import "iTermTextExtractor.h"
#import "iTermThroughputEstimator.h"
#import "iTermUpdateCadenceController.h"
#import "iTermWarning.h"
#import "MovePaneController.h"
#import "MovingAverage.h"
Loading
Loading
@@ -206,18 +207,6 @@ static NSTimeInterval kMinimumPartialLineTriggerCheckInterval = 0.5;
// shuold be sent.
static const NSTimeInterval kAntiIdleGracePeriod = 0.1;
 
// Timer period between updates when active (not idle, tab is visible or title bar is changing,
// etc.)
static const NSTimeInterval kActiveUpdateCadence = 1 / 20.0;
// Timer period between updates when adaptive frame rate is enabled and throughput is low but not 0.
static const NSTimeInterval kFastUpdateCadence = 1.0 / 60.0;
// Timer period for background sessions. This changes the tab item's color
// so it must run often enough for that to be useful.
// TODO(georgen): There's room for improvement here.
static const NSTimeInterval kBackgroundUpdateCadence = 1;
// Limit for number of entries in self.directories, self.commands, self.hosts.
// Keeps saved state from exploding like in issue 5029.
static const NSUInteger kMaxDirectories = 100;
Loading
Loading
@@ -229,7 +218,8 @@ static const NSUInteger kMaxHosts = 100;
iTermCoprocessDelegate,
iTermHotKeyNavigableSession,
iTermPasteHelperDelegate,
iTermSessionViewDelegate>
iTermSessionViewDelegate,
iTermUpdateCadenceControllerDelegate>
@property(nonatomic, retain) Interval *currentMarkOrNotePosition;
@property(nonatomic, retain) TerminalFile *download;
@property(nonatomic, retain) TerminalFileUpload *upload;
Loading
Loading
@@ -294,15 +284,6 @@ static const NSUInteger kMaxHosts = 100;
// top margin above the textview.
TextViewWrapper *_wrapper;
 
BOOL _useGCDUpdateTimer;
// This timer fires periodically to redraw textview, update the scroll position, tab appearance,
// etc.
NSTimer *_updateTimer;
// This is the experimental GCD version of the update timer that seems to have more regular refreshes.
dispatch_source_t _gcdUpdateTimer;
NSTimeInterval _cadence;
// Anti-idle timer that sends a character every so often to the host.
NSTimer *_antiIdleTimer;
 
Loading
Loading
@@ -479,6 +460,8 @@ static const NSUInteger kMaxHosts = 100;
 
// Absolute line number where touchbar status changed.
long long _statusChangedAbsLine;
iTermUpdateCadenceController *_cadenceController;
}
 
+ (void)registerSessionInArrangement:(NSDictionary *)arrangement {
Loading
Loading
@@ -510,7 +493,6 @@ static const NSUInteger kMaxHosts = 100;
_useAdaptiveFrameRate = [iTermAdvancedSettingsModel useAdaptiveFrameRate];
_adaptiveFrameRateThroughputThreshold = [iTermAdvancedSettingsModel adaptiveFrameRateThroughputThreshold];
_slowFrameRate = [iTermAdvancedSettingsModel slowFrameRate];
_useGCDUpdateTimer = [iTermAdvancedSettingsModel useGCDUpdateTimer];
_idleTime = [iTermAdvancedSettingsModel idleTimeSeconds];
_triggerLineNumber = -1;
_fakePromptDetectedAbsLine = -1;
Loading
Loading
@@ -553,6 +535,8 @@ static const NSUInteger kMaxHosts = 100;
// Allocate a guid. If we end up restoring from a session during startup this will be replaced.
_guid = [[NSString uuid] retain];
_throughputEstimator = [[iTermThroughputEstimator alloc] initWithHistoryOfDuration:5.0 / 30.0 secondsPerBucket:1 / 30.0];
_cadenceController = [[iTermUpdateCadenceController alloc] initWithThroughputEstimator:_throughputEstimator];
_cadenceController.delegate = self;
 
_keystrokeSubscriptions = [[NSMutableDictionary alloc] init];
_updateSubscriptions = [[NSMutableDictionary alloc] init];
Loading
Loading
@@ -646,10 +630,7 @@ ITERM_WEAKLY_REFERENCEABLE
[_backgroundImagePath release];
[_backgroundImage release];
[_antiIdleTimer invalidate];
if (_gcdUpdateTimer != nil) {
dispatch_source_cancel(_gcdUpdateTimer);
dispatch_release(_gcdUpdateTimer);
}
[_cadenceController release];
[_originalProfile release];
[_liveSession release];
[_tmuxGateway release];
Loading
Loading
@@ -2372,7 +2353,7 @@ ITERM_WEAKLY_REFERENCEABLE
dispatch_semaphore_signal(_executionSemaphore);
dispatch_release(_executionSemaphore);
[self release];
});
});
}
 
- (void)synchronousReadTask:(NSString *)string {
Loading
Loading
@@ -4208,100 +4189,11 @@ ITERM_WEAKLY_REFERENCEABLE
}
}
 
- (BOOL)updateTimerIsValid {
if (_useGCDUpdateTimer) {
return _gcdUpdateTimer != nil;
} else {
return _updateTimer.isValid;
}
}
- (void)setActive:(BOOL)active {
DLog(@"setActive:%@ timerRunning=%@ updateTimer.isValue=%@ lastTimeout=%f session=%@",
@(active), @(_timerRunning), @(self.updateTimerIsValid), _lastTimeout, self);
@(active), @(_timerRunning), @(_cadenceController.updateTimerIsValid), _lastTimeout, self);
_active = active;
[self changeCadenceIfNeeded];
}
- (void)changeCadenceIfNeeded {
BOOL effectivelyActive = (_active || !self.isIdle || [NSApp isActive]);
if (effectivelyActive && [_delegate sessionBelongsToVisibleTab]) {
if (_useAdaptiveFrameRate) {
const NSInteger kThroughputLimit = _adaptiveFrameRateThroughputThreshold;
const NSInteger estimatedThroughput = [_throughputEstimator estimatedThroughput];
if (estimatedThroughput < kThroughputLimit && estimatedThroughput > 0) {
[self setUpdateCadence:kFastUpdateCadence];
} else {
[self setUpdateCadence:1.0 / _slowFrameRate];
}
} else {
[self setUpdateCadence:kActiveUpdateCadence];
}
} else {
[self setUpdateCadence:kBackgroundUpdateCadence];
}
}
- (void)setUpdateCadence:(NSTimeInterval)cadence {
if (_useGCDUpdateTimer) {
[self setGCDUpdateCadence:cadence];
} else {
[self setTimerUpdateCadence:cadence];
}
}
- (void)setTimerUpdateCadence:(NSTimeInterval)cadence {
if (_updateTimer.timeInterval == cadence) {
DLog(@"No change to cadence.");
return;
}
DLog(@"Set cadence of %@ to %f", self, cadence);
[_updateTimer invalidate];
if (_inLiveResize) {
// This solves the bug where we don't redraw properly during live resize.
// I'm worried about the possible side effects it might have since there's no way to
// know all the tracking event loops.
_updateTimer = [NSTimer timerWithTimeInterval:kActiveUpdateCadence
target:self.weakSelf
selector:@selector(updateDisplay)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_updateTimer forMode:NSRunLoopCommonModes];
} else {
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:cadence
target:self.weakSelf
selector:@selector(updateDisplay)
userInfo:nil
repeats:YES];
}
}
- (void)setGCDUpdateCadence:(NSTimeInterval)cadence {
const NSTimeInterval period = _inLiveResize ? kActiveUpdateCadence : cadence;
if (_cadence == period) {
DLog(@"No change to cadence.");
return;
}
DLog(@"Set cadence of %@ to %f", self, cadence);
_cadence = period;
if (_gcdUpdateTimer != nil) {
dispatch_source_cancel(_gcdUpdateTimer);
dispatch_release(_gcdUpdateTimer);
_gcdUpdateTimer = nil;
}
_gcdUpdateTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_gcdUpdateTimer,
dispatch_walltime(NULL, 0),
period * NSEC_PER_SEC,
0.005 * NSEC_PER_SEC);
PTYSession *weakSelf = self.weakSelf;
dispatch_source_set_event_handler(_gcdUpdateTimer, ^{
[weakSelf updateDisplay];
});
dispatch_resume(_gcdUpdateTimer);
[_cadenceController changeCadenceIfNeeded];
}
 
- (void)doAntiIdle {
Loading
Loading
@@ -4457,11 +4349,7 @@ ITERM_WEAKLY_REFERENCEABLE
if ([iTermAdvancedSettingsModel trackingRunloopForLiveResize]) {
if (notification.object == self.textview.window) {
_inLiveResize = YES;
if (!_useGCDUpdateTimer) {
if (_updateTimer) {
[[NSRunLoop currentRunLoop] addTimer:_updateTimer forMode:NSRunLoopCommonModes];
}
}
[_cadenceController willStartLiveResize];
}
}
}
Loading
Loading
@@ -4470,18 +4358,7 @@ ITERM_WEAKLY_REFERENCEABLE
if ([iTermAdvancedSettingsModel trackingRunloopForLiveResize]) {
if (notification.object == self.textview.window) {
_inLiveResize = NO;
if (_useGCDUpdateTimer) {
NSTimeInterval cadence = _cadence;
_cadence = 0;
[self setUpdateCadence:cadence];
} else {
if (_updateTimer) {
NSTimeInterval cadence = _updateTimer.timeInterval;
[_updateTimer invalidate];
_updateTimer = nil;
[self setUpdateCadence:cadence];
}
}
[_cadenceController liveResizeDidEnd];
}
}
}
Loading
Loading
@@ -9054,6 +8931,24 @@ ITERM_WEAKLY_REFERENCEABLE
[self queueAnnouncement:announcement identifier:[[NSUUID UUID] UUIDString]];
}
 
#pragma mark - iTermUpdateCadenceController
- (void)updateCadenceControllerUpdateDisplay:(iTermUpdateCadenceController *)controller {
[self updateDisplay];
}
- (iTermUpdateCadenceState)updateCadenceControllerState {
iTermUpdateCadenceState state;
state.active = _active;
state.idle = self.isIdle;
state.visible = [_delegate sessionBelongsToVisibleTab];
state.useAdaptiveFrameRate = _useAdaptiveFrameRate;
state.adaptiveFrameRateThroughputThreshold = _adaptiveFrameRateThroughputThreshold;
state.slowFrameRate = _slowFrameRate;
state.liveResizing = _inLiveResize;
return state;
}
#pragma mark - API
 
- (NSString *)stringForLine:(screen_char_t *)screenChars
Loading
Loading
Loading
Loading
@@ -7,59 +7,7 @@
//
 
#import "iTermRateLimitedUpdate.h"
@interface iTermTimerProxy : NSObject
- (void)performBlock:(NSTimer *)timer;
@end
// The timer keeps a strong reference to the proxy, while the proxy's block can
// hold a weak reference to the true target.
@implementation iTermTimerProxy
- (void)performBlock:(NSTimer *)timer {
void (^block)(NSTimer * _Nonnull) = timer.userInfo;
if (block != nil) {
block(timer);
}
}
@end
@interface NSTimer (iTerm)
+ (instancetype)it_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void (^_Nonnull)(NSTimer * _Nonnull timer))block;
+ (instancetype)it_weakTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats target:(id)target selector:(SEL)selector;
@end
@implementation NSTimer (iTerm)
+ (instancetype)it_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
repeats:(BOOL)repeats
block:(void (^_Nonnull)(NSTimer * _Nonnull timer))block {
iTermTimerProxy *proxy = [[iTermTimerProxy alloc] init];
return [NSTimer scheduledTimerWithTimeInterval:timeInterval
target:proxy
selector:@selector(performBlock:)
userInfo:[block copy]
repeats:repeats];
}
+ (instancetype)it_weakTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats target:(id)target selector:(SEL)selector {
__weak id weakTarget = target;
return [self it_scheduledTimerWithTimeInterval:timeInterval repeats:repeats block:^(NSTimer * _Nonnull timer) {
[timer it_performSelector:selector onTarget:weakTarget];
}];
}
- (void)it_performSelector:(SEL)selector onTarget:(id)target {
if (target) {
void (*func)(id, SEL, NSTimer *) = (void *)[target methodForSelector:selector];
func(target, selector, self);
}
}
@end
#import "NSTimer+iTerm.h"
 
@implementation iTermRateLimitedUpdate {
// While nonnil, block will not be performed.
Loading
Loading
@@ -76,10 +24,11 @@
- (void)performRateLimitedBlock:(void (^)())block {
if (_timer == nil) {
block();
_timer = [NSTimer it_weakTimerWithTimeInterval:self.minimumInterval
repeats:NO
target:self
selector:@selector(performBlockIfNeeded:)];
_timer = [NSTimer scheduledWeakTimerWithTimeInterval:self.minimumInterval
target:self
selector:@selector(performBlockIfNeeded:)
userInfo:nil
repeats:NO];
} else {
_block = [block copy];
}
Loading
Loading
//
// iTermUpdateCadenceController.h
// iTerm2
//
// Created by George Nachman on 8/1/17.
//
//
#import <Cocoa/Cocoa.h>
@class iTermThroughputEstimator;
@class iTermUpdateCadenceController;
typedef struct {
BOOL active;
BOOL idle;
BOOL visible;
BOOL useAdaptiveFrameRate;
NSInteger adaptiveFrameRateThroughputThreshold;
double slowFrameRate;
BOOL liveResizing;
} iTermUpdateCadenceState;
@protocol iTermUpdateCadenceControllerDelegate<NSObject>
// Time to update the display.
- (void)updateCadenceControllerUpdateDisplay:(iTermUpdateCadenceController *)controller;
// Returns the current state of the delegate.
- (iTermUpdateCadenceState)updateCadenceControllerState;
@end
@interface iTermUpdateCadenceController : NSObject
@property (nonatomic, readonly) BOOL updateTimerIsValid;
@property (nonatomic, weak) id<iTermUpdateCadenceControllerDelegate> delegate;
- (instancetype)initWithThroughputEstimator:(iTermThroughputEstimator *)throughputEstimator NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (void)changeCadenceIfNeeded;
- (void)willStartLiveResize;
- (void)liveResizeDidEnd;
@end
//
// iTermUpdateCadenceController.m
// iTerm2
//
// Created by George Nachman on 8/1/17.
//
//
#import "iTermUpdateCadenceController.h"
#import "DebugLogging.h"
#import "NSTimer+iTerm.h"
#import "iTermAdvancedSettingsModel.h"
#import "iTermThroughputEstimator.h"
// Timer period between updates when active (not idle, tab is visible or title bar is changing,
// etc.)
static const NSTimeInterval kActiveUpdateCadence = 1 / 20.0;
// Timer period between updates when adaptive frame rate is enabled and throughput is low but not 0.
static const NSTimeInterval kFastUpdateCadence = 1.0 / 60.0;
// Timer period for background sessions. This changes the tab item's color
// so it must run often enough for that to be useful.
// TODO(georgen): There's room for improvement here.
static const NSTimeInterval kBackgroundUpdateCadence = 1;
@implementation iTermUpdateCadenceController {
BOOL _useGCDUpdateTimer;
// This timer fires periodically to redraw textview, update the scroll position, tab appearance,
// etc.
NSTimer *_updateTimer;
// This is the experimental GCD version of the update timer that seems to have more regular refreshes.
dispatch_source_t _gcdUpdateTimer;
NSTimeInterval _cadence;
BOOL _deferredCadenceChange;
iTermThroughputEstimator *_throughputEstimator;
}
- (instancetype)initWithThroughputEstimator:(iTermThroughputEstimator *)throughputEstimator {
self = [super init];
if (self) {
_useGCDUpdateTimer = [iTermAdvancedSettingsModel useGCDUpdateTimer];
_throughputEstimator = throughputEstimator;
}
return self;
}
- (void)dealloc {
if (_gcdUpdateTimer != nil) {
dispatch_source_cancel(_gcdUpdateTimer);
}
[_updateTimer invalidate];
}
- (void)changeCadenceIfNeeded {
[self changeCadenceIfNeeded:NO];
}
- (void)willStartLiveResize {
if (!_useGCDUpdateTimer && _updateTimer) {
[[NSRunLoop currentRunLoop] addTimer:_updateTimer forMode:NSRunLoopCommonModes];
}
}
- (void)liveResizeDidEnd {
if (_useGCDUpdateTimer) {
NSTimeInterval cadence = _cadence;
_cadence = 0;
[self setUpdateCadence:cadence liveResizing:NO force:NO];
} else {
if (_updateTimer) {
NSTimeInterval cadence = _updateTimer.timeInterval;
[_updateTimer invalidate];
_updateTimer = nil;
[self setUpdateCadence:cadence liveResizing:NO force:NO];
}
}
}
#pragma mark - Private
- (void)changeCadenceIfNeeded:(BOOL)force {
iTermUpdateCadenceState state = [_delegate updateCadenceControllerState];
BOOL effectivelyActive = (state.active || !state.idle || [NSApp isActive]);
if (effectivelyActive && state.visible) {
if (state.useAdaptiveFrameRate) {
const NSInteger kThroughputLimit = state.adaptiveFrameRateThroughputThreshold;
const NSInteger estimatedThroughput = [_throughputEstimator estimatedThroughput];
if (estimatedThroughput < kThroughputLimit && estimatedThroughput > 0) {
[self setUpdateCadence:kFastUpdateCadence liveResizing:state.liveResizing force:force];
} else {
[self setUpdateCadence:1.0 / state.slowFrameRate liveResizing:state.liveResizing force:force];
}
} else {
[self setUpdateCadence:kActiveUpdateCadence liveResizing:state.liveResizing force:force];
}
} else {
[self setUpdateCadence:kBackgroundUpdateCadence liveResizing:state.liveResizing force:force];
}
}
- (void)setUpdateCadence:(NSTimeInterval)cadence liveResizing:(BOOL)liveResizing force:(BOOL)force {
if (_useGCDUpdateTimer) {
[self setGCDUpdateCadence:cadence liveResizing:liveResizing force:force];
} else {
[self setTimerUpdateCadence:cadence liveResizing:liveResizing force:force];
}
}
- (void)setTimerUpdateCadence:(NSTimeInterval)cadence liveResizing:(BOOL)liveResizing force:(BOOL)force {
if (_updateTimer.timeInterval == cadence) {
DLog(@"No change to cadence.");
return;
}
DLog(@"Set cadence of %@ to %f", self.delegate, cadence);
if (liveResizing) {
// This solves the bug where we don't redraw properly during live resize.
// I'm worried about the possible side effects it might have since there's no way to
// know all the tracking event loops.
[_updateTimer invalidate];
_updateTimer = [NSTimer weakTimerWithTimeInterval:kActiveUpdateCadence
target:self
selector:@selector(updateDisplay)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_updateTimer forMode:NSRunLoopCommonModes];
} else {
if (!force && _updateTimer && cadence > _updateTimer.timeInterval) {
DLog(@"Defer cadence change");
_deferredCadenceChange = YES;
} else {
[_updateTimer invalidate];
_updateTimer = [NSTimer scheduledWeakTimerWithTimeInterval:cadence
target:self
selector:@selector(updateDisplay)
userInfo:nil
repeats:YES];
}
}
}
- (void)setGCDUpdateCadence:(NSTimeInterval)cadence liveResizing:(BOOL)liveResizing force:(BOOL)force {
const NSTimeInterval period = liveResizing ? kActiveUpdateCadence : cadence;
if (_cadence == period) {
DLog(@"No change to cadence.");
return;
}
DLog(@"Set cadence of %@ to %f", self.delegate, cadence);
if (!force && _cadence > 0 && cadence > _cadence) {
// Don't increase the cadence until after the screen has a chance to
// draw. This way if you do "cat bigfile.txt" you see the first
// screenful before the refresh rate drops. This way you know
// something's happening.
DLog(@"Defer cadence change");
_deferredCadenceChange = YES;
return;
}
_cadence = period;
if (_gcdUpdateTimer != nil) {
dispatch_source_cancel(_gcdUpdateTimer);
_gcdUpdateTimer = nil;
}
_gcdUpdateTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_gcdUpdateTimer,
dispatch_time(DISPATCH_TIME_NOW, period * NSEC_PER_SEC),
period * NSEC_PER_SEC,
0.005 * NSEC_PER_SEC);
__weak __typeof(self) weakSelf = self;
dispatch_source_set_event_handler(_gcdUpdateTimer, ^{
[weakSelf updateDisplay];
});
dispatch_resume(_gcdUpdateTimer);
}
- (BOOL)updateTimerIsValid {
if (_useGCDUpdateTimer) {
return _gcdUpdateTimer != nil;
} else {
return _updateTimer.isValid;
}
}
- (void)updateDisplay {
if (_deferredCadenceChange) {
[self changeCadenceIfNeeded:YES];
_deferredCadenceChange = NO;
}
[_delegate updateCadenceControllerUpdateDisplay:self];
}
@end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment