diff options
Diffstat (limited to 'platform/ios/app/MBXFrameTimeGraphView.m')
-rw-r--r-- | platform/ios/app/MBXFrameTimeGraphView.m | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/platform/ios/app/MBXFrameTimeGraphView.m b/platform/ios/app/MBXFrameTimeGraphView.m new file mode 100644 index 0000000000..f689768818 --- /dev/null +++ b/platform/ios/app/MBXFrameTimeGraphView.m @@ -0,0 +1,140 @@ +#import "MBXFrameTimeGraphView.h" + +const CGFloat MBXFrameTimeExaggeration = 4.f * 1000.f; +const CGFloat MBXFrameTimeBarWidth = 4.f; + +@interface MBXFrameTimeGraphView () + +@property (nonatomic) CAScrollLayer *scrollLayer; +@property (nonatomic) CAShapeLayer *thresholdLayer; +@property (nonatomic) CGFloat currentX; +@property (nonatomic) NSMutableArray<CAShapeLayer *> *barLayers; + +@property (nonatomic) UIColor *safeColor; +@property (nonatomic) UIColor *warningColor; +@property (nonatomic) UIColor *dangerColor; + +@end + +@implementation MBXFrameTimeGraphView + +- (instancetype)init { + if (self = [super init]) { + [self commonInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self commonInit]; + } + return self; +} + +- (void)commonInit { + self.userInteractionEnabled = NO; + self.layer.opacity = 0.9f; + + self.scrollLayer = [CAScrollLayer layer]; + self.scrollLayer.scrollMode = kCAScrollHorizontally; + self.scrollLayer.masksToBounds = YES; + [self.layer addSublayer:self.scrollLayer]; + + self.thresholdLayer = [CAShapeLayer layer]; + self.thresholdLayer.fillColor = [UIColor darkGrayColor].CGColor; + [self.layer addSublayer:self.thresholdLayer]; + + self.barLayers = [NSMutableArray array]; + + self.safeColor = [UIColor colorWithRed:(CGFloat)(0.f/255.f) green:(CGFloat)(190.f/255.f) blue:(CGFloat)(123.f/255.f) alpha:1.f]; + self.warningColor = [UIColor colorWithRed:(CGFloat)(255.f/255.f) green:(CGFloat)(154.f/255.f) blue:(CGFloat)(82.f/255.f) alpha:1.f]; + self.dangerColor = [UIColor colorWithRed:(CGFloat)(255.f/255.f) green:(CGFloat)(91.f/255.f) blue:(CGFloat)(86.f/255.f) alpha:1.f]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + if (!CGRectEqualToRect(self.scrollLayer.frame, self.bounds)) { + self.scrollLayer.frame = self.bounds; + + CGRect thresholdLineRect = CGRectMake(0, self.frame.size.height - [self renderDurationTargetMilliseconds], self.frame.size.width, 1); + self.thresholdLayer.path = CGPathCreateWithRect(thresholdLineRect, nil); + } +} + +- (void)updatePathWithFrameDuration:(CFTimeInterval)frameDuration { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + + self.currentX += MBXFrameTimeBarWidth; + + CAShapeLayer *bar = [self barWithFrameDuration:frameDuration]; + bar.position = CGPointMake(self.currentX, self.frame.size.height); + + [self.scrollLayer addSublayer:bar]; + [self.barLayers addObject:bar]; + + [self.scrollLayer scrollToPoint:CGPointMake(self.currentX - self.frame.size.width, 0)]; + + [self removeStaleBars]; + + [CATransaction commit]; +} + +- (CGFloat)renderDurationTargetMilliseconds { + CGFloat maximumFramesPerSecond; + if (@available(iOS 10.3, *)) { + maximumFramesPerSecond = UIScreen.mainScreen.maximumFramesPerSecond; + } else { + // Not always strictly accurate, but works as an expedient approximation. + maximumFramesPerSecond = 60; + } + + CGFloat target = (1.0 / maximumFramesPerSecond) * MBXFrameTimeExaggeration; + return [self roundedFloat:target]; +} + +- (CGFloat)roundedFloat:(CGFloat)f { +#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + CGFloat scaleFactor = [UIScreen mainScreen].nativeScale; +#elif TARGET_OS_MAC + CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor; +#endif + return round(f * scaleFactor) / scaleFactor; +} + +- (CAShapeLayer *)barWithFrameDuration:(CFTimeInterval)frameDuration { + CAShapeLayer *bar = [CAShapeLayer layer]; + + CGRect barRect = CGRectMake(0, 0, MBXFrameTimeBarWidth, -(fminf(frameDuration * MBXFrameTimeExaggeration, self.frame.size.height))); + UIBezierPath *barPath = [UIBezierPath bezierPathWithRect:barRect]; + bar.path = barPath.CGPath; + bar.fillColor = [self colorForFrameDuration:frameDuration].CGColor; + + return bar; +} + +- (UIColor *)colorForFrameDuration:(CFTimeInterval)frameDuration { + CGFloat renderDurationTargetMilliseconds = [self renderDurationTargetMilliseconds]; + frameDuration *= MBXFrameTimeExaggeration; + + if (frameDuration < renderDurationTargetMilliseconds && frameDuration > (renderDurationTargetMilliseconds * 0.75)) { + return self.warningColor; + } else if (frameDuration > renderDurationTargetMilliseconds) { + return self.dangerColor; + } else { + return self.safeColor; + } +} + +- (void)removeStaleBars { + if (self.barLayers.count > (self.frame.size.width / MBXFrameTimeBarWidth * 3)) { + NSRange staleBarsRange = NSMakeRange(0, self.frame.size.width / MBXFrameTimeBarWidth); + NSArray *staleBars = [self.barLayers subarrayWithRange:staleBarsRange]; + [staleBars makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; + [self.barLayers removeObjectsInRange:staleBarsRange]; + } +} + +@end |