summaryrefslogtreecommitdiff
path: root/platform/ios/app/MBXFrameTimeGraphView.m
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/app/MBXFrameTimeGraphView.m')
-rw-r--r--platform/ios/app/MBXFrameTimeGraphView.m140
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