1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#import "DragLatencyController.h" 18 19#import <dispatch/dispatch.h> 20#import <math.h> 21#import <numeric> 22#import <vector> 23 24#import "UIAlertView+Extensions.h" 25#import "WALTAppDelegate.h" 26#import "WALTClient.h" 27#import "WALTLogger.h" 28#import "WALTTouch.h" 29 30static const NSTimeInterval kGoalpostFrequency = 0.55; // TODO(pquinn): User-configurable settings. 31static const NSUInteger kMinTouchEvents = 100; 32static const NSUInteger kMinLaserEvents = 8; 33static const char kWALTLaserTag = 'L'; 34 35@interface WALTLaserEvent : NSObject 36@property (assign) NSTimeInterval t; 37@property (assign) int value; 38@end 39 40@implementation WALTLaserEvent 41@end 42 43/** Linear interpolation between x0 and x1 at alpha. */ 44template <typename T> 45static T Lerp(const T& x0, const T& x1, double alpha) { 46 NSCAssert(alpha >= 0 && alpha <= 1, @"alpha must be between 0 and 1 (%f)", alpha); 47 return ((1 - alpha) * x0) + (alpha * x1); 48} 49 50/** Linear interpolation of (xp, yp) at x. */ 51template <typename S, typename T> 52static std::vector<T> Interpolate(const std::vector<S>& x, 53 const std::vector<S>& xp, 54 const std::vector<T>& yp) { 55 NSCAssert(xp.size(), @"xp must contain at least one value."); 56 NSCAssert(xp.size() == yp.size(), @"xp and yp must have matching lengths."); 57 58 std::vector<T> y; 59 y.reserve(x.size()); 60 61 size_t i = 0; // Index into x. 62 63 for (; i < x.size() && x[i] < xp.front(); ++i) { 64 y.push_back(yp.front()); // Pad out y with yp.front() for x values before xp.front(). 65 } 66 67 size_t ip = 0; // Index into xp/yp. 68 69 for (; ip < xp.size() && i < x.size(); ++i) { 70 while (ip < xp.size() && xp[ip] <= x[i]) { // Find an xp[ip] greater than x[i]. 71 ++ip; 72 } 73 if (ip >= xp.size()) { 74 break; // Ran out of values. 75 } 76 77 const double alpha = (x[i] - xp[ip - 1]) / static_cast<double>(xp[ip] - xp[ip - 1]); 78 y.push_back(Lerp(yp[ip - 1], yp[ip], alpha)); 79 } 80 81 for (; i < x.size(); ++i) { 82 y.push_back(yp.back()); // Pad out y with yp.back() for values after xp.back(). 83 } 84 85 return y; 86} 87 88/** Extracts the values of y where the corresponding value in x is equal to value. */ 89template <typename S, typename T> 90static std::vector<S> Extract(const std::vector<T>& x, const std::vector<S>& y, const T& value) { 91 NSCAssert(x.size() == y.size(), @"x and y must have matching lengths."); 92 std::vector<S> extracted; 93 94 for (size_t i = 0; i < x.size(); ++i) { 95 if (x[i] == value) { 96 extracted.push_back(y[i]); 97 } 98 } 99 100 return extracted; 101} 102 103/** Returns the standard deviation of the values in x. */ 104template <typename T> 105static T StandardDeviation(const std::vector<T>& x) { 106 NSCAssert(x.size() > 0, @"x must have at least one value."); 107 const T sum = std::accumulate(x.begin(), x.end(), T{}); 108 const T mean = sum / x.size(); 109 const T ss = std::accumulate(x.begin(), x.end(), T{}, ^(T accum, T value){ 110 return accum + ((value - mean) * (value - mean)); 111 }); 112 return sqrt(ss / (x.size() - 1)); 113} 114 115/** Returns the index of the smallest value in x. */ 116template <typename T> 117static size_t ArgMin(const std::vector<T>& x) { 118 NSCAssert(x.size() > 0, @"x must have at least one value."); 119 size_t imin = 0; 120 for (size_t i = 1; i < x.size(); ++i) { 121 if (x[i] < x[imin]) { 122 imin = i; 123 } 124 } 125 return imin; 126} 127 128/** 129 * Finds a positive time value that shifting laserTs by will minimise the standard deviation of 130 * interpolated touchYs. 131 */ 132static NSTimeInterval FindBestShift(const std::vector<NSTimeInterval>& laserTs, 133 const std::vector<NSTimeInterval>& touchTs, 134 const std::vector<CGFloat>& touchYs) { 135 NSCAssert(laserTs.size() > 0, @"laserTs must have at least one value."); 136 NSCAssert(touchTs.size() == touchYs.size(), @"touchTs and touchYs must have matching lengths."); 137 138 const NSTimeInterval kSearchCoverage = 0.15; 139 const int kSteps = 1500; 140 const NSTimeInterval kShiftStep = kSearchCoverage / kSteps; 141 142 std::vector<NSTimeInterval> deviations; 143 deviations.reserve(kSteps); 144 145 std::vector<NSTimeInterval> ts(laserTs.size()); 146 for (int i = 0; i < kSteps; ++i) { 147 for (size_t j = 0; j < laserTs.size(); ++j) { 148 ts[j] = laserTs[j] + (kShiftStep * i); 149 } 150 151 std::vector<CGFloat> laserYs = Interpolate(ts, touchTs, touchYs); 152 deviations.push_back(StandardDeviation(laserYs)); 153 } 154 155 return ArgMin(deviations) * kShiftStep; 156} 157 158@interface DragLatencyController () 159- (void)updateCountDisplay; 160- (void)processEvent:(UIEvent *)event; 161- (void)receiveTriggers:(id)context; 162- (void)stopReceiver; 163@end 164 165@implementation DragLatencyController { 166 WALTClient *_client; 167 WALTLogger *_logger; 168 169 NSMutableArray<WALTTouch *> *_touchEvents; 170 NSMutableArray<WALTLaserEvent *> *_laserEvents; 171 172 NSThread *_triggerReceiver; 173 dispatch_semaphore_t _receiverComplete; 174} 175 176- (void)dealloc { 177 [self stopReceiver]; 178} 179 180- (void)viewDidLoad { 181 [super viewDidLoad]; 182 183 _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client; 184 _logger = [WALTLogger sessionLogger]; 185} 186 187- (void)viewWillAppear:(BOOL)animated { 188 [super viewWillAppear:animated]; 189 190 [self updateCountDisplay]; 191 192 [_logger appendString:@"DRAGLATENCY\n"]; 193} 194 195- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 196 [self processEvent:event]; 197} 198 199- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 200 [self processEvent:event]; 201} 202 203- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 204 [self processEvent:event]; 205} 206 207- (void)processEvent:(UIEvent *)event { 208 // TODO(pquinn): Pull out coalesced touches. 209 210 WALTTouch *touch = [[WALTTouch alloc] initWithEvent:event]; 211 [_touchEvents addObject:touch]; 212 [_logger appendFormat:@"TOUCH\t%.3f\t%.2f\t%.2f\n", 213 touch.kernelTime, touch.location.x, touch.location.y]; 214 [self updateCountDisplay]; 215} 216 217- (void)updateCountDisplay { 218 NSString *counts = [NSString stringWithFormat:@"N ✛ %lu ⇄ %lu", 219 (unsigned long)_laserEvents.count, (unsigned long)_touchEvents.count]; 220 self.countLabel.text = counts; 221} 222 223- (IBAction)start:(id)sender { 224 [self reset:sender]; 225 226 self.goalpostView.hidden = NO; 227 self.statusLabel.text = @""; 228 229 [UIView beginAnimations:@"Goalpost" context:NULL]; 230 [UIView setAnimationDuration:kGoalpostFrequency]; 231 [UIView setAnimationBeginsFromCurrentState:NO]; 232 [UIView setAnimationRepeatCount:FLT_MAX]; 233 [UIView setAnimationRepeatAutoreverses:YES]; 234 235 self.goalpostView.transform = 236 CGAffineTransformMakeTranslation(0.0, -CGRectGetHeight(self.view.frame) + 300); 237 238 [UIView commitAnimations]; 239 240 _receiverComplete = dispatch_semaphore_create(0); 241 _triggerReceiver = [[NSThread alloc] initWithTarget:self 242 selector:@selector(receiveTriggers:) 243 object:nil]; 244 [_triggerReceiver start]; 245} 246 247- (IBAction)reset:(id)sender { 248 [self stopReceiver]; 249 250 self.goalpostView.transform = CGAffineTransformMakeTranslation(0.0, 0.0); 251 self.goalpostView.hidden = YES; 252 253 _touchEvents = [[NSMutableArray<WALTTouch *> alloc] init]; 254 _laserEvents = [[NSMutableArray<WALTLaserEvent *> alloc] init]; 255 256 [self updateCountDisplay]; 257 258 NSError *error = nil; 259 if (![_client syncClocksWithError:&error]) { 260 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 261 [alert show]; 262 } 263 264 [_logger appendString:@"RESET\n"]; 265} 266 267- (void)receiveTriggers:(id)context { 268 // Turn on laser change notifications. 269 NSError *error = nil; 270 if (![_client sendCommand:WALTLaserOnCommand error:&error]) { 271 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 272 [alert show]; 273 dispatch_semaphore_signal(_receiverComplete); 274 return; 275 } 276 277 NSData *response = [_client readResponseWithTimeout:kWALTReadTimeout]; 278 if (![_client checkResponse:response forCommand:WALTLaserOnCommand]) { 279 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" 280 message:@"Failed to start laser probe." 281 delegate:nil 282 cancelButtonTitle:@"Dismiss" 283 otherButtonTitles:nil]; 284 [alert show]; 285 dispatch_semaphore_signal(_receiverComplete); 286 return; 287 } 288 289 while (!NSThread.currentThread.isCancelled) { 290 WALTTrigger response = [_client readTriggerWithTimeout:kWALTReadTimeout]; 291 if (response.tag == kWALTLaserTag) { 292 WALTLaserEvent *event = [[WALTLaserEvent alloc] init]; 293 event.t = response.t; 294 event.value = response.value; 295 [_laserEvents addObject:event]; 296 [_logger appendFormat:@"LASER\t%.3f\t%d\n", event.t, event.value]; 297 } else if (response.tag != '\0') { // Don't fail for timeout errors. 298 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" 299 message:@"Failed to read laser probe." 300 delegate:nil 301 cancelButtonTitle:@"Dismiss" 302 otherButtonTitles:nil]; 303 [alert show]; 304 } 305 } 306 307 // Turn off laser change notifications. 308 [_client sendCommand:WALTLaserOffCommand error:nil]; 309 [_client readResponseWithTimeout:kWALTReadTimeout]; 310 311 dispatch_semaphore_signal(_receiverComplete); 312} 313 314- (void)stopReceiver { 315 // TODO(pquinn): This will deadlock if called in rapid succession -- there is a small delay 316 // between dispatch_semaphore_signal() and -[NSThread isExecuting] changing. 317 // Unfortunately, NSThread is not joinable... 318 if (_triggerReceiver.isExecuting) { 319 [_triggerReceiver cancel]; 320 dispatch_semaphore_wait(_receiverComplete, DISPATCH_TIME_FOREVER); 321 } 322} 323 324- (IBAction)computeStatistics:(id)sender { 325 if (_touchEvents.count < kMinTouchEvents) { 326 self.statusLabel.text = 327 [NSString stringWithFormat:@"Too few touch events (%lu/%lu).", 328 (unsigned long)_touchEvents.count, (unsigned long)kMinTouchEvents]; 329 [self reset:sender]; 330 return; 331 } 332 333 // Timestamps are reset to be relative to t0 to make the output easier to read. 334 const NSTimeInterval t0 = _touchEvents.firstObject.kernelTime; 335 const NSTimeInterval tF = _touchEvents.lastObject.kernelTime; 336 337 std::vector<NSTimeInterval> ft(_touchEvents.count); 338 std::vector<CGFloat> fy(_touchEvents.count); 339 for (NSUInteger i = 0; i < _touchEvents.count; ++i) { 340 ft[i] = _touchEvents[i].kernelTime - t0; 341 fy[i] = _touchEvents[i].location.y; 342 } 343 344 // Remove laser events that have a timestamp outside [t0, tF]. 345 [_laserEvents filterUsingPredicate:[NSPredicate predicateWithBlock: 346 ^BOOL(WALTLaserEvent *evaluatedObject, NSDictionary<NSString *, id> *bindings) { 347 return evaluatedObject.t >= t0 && evaluatedObject.t <= tF; 348 }]]; 349 350 if (_laserEvents.count < kMinLaserEvents) { 351 self.statusLabel.text = 352 [NSString stringWithFormat:@"Too few laser events (%lu/%lu).", 353 (unsigned long)_laserEvents.count, (unsigned long)kMinLaserEvents]; 354 [self reset:sender]; 355 return; 356 } 357 358 if (_laserEvents.firstObject.value != 0) { 359 self.statusLabel.text = @"First laser crossing was not into the beam."; 360 [self reset:sender]; 361 return; 362 } 363 364 std::vector<NSTimeInterval> lt(_laserEvents.count); 365 std::vector<int> lv(_laserEvents.count); 366 for (NSUInteger i = 0; i < _laserEvents.count; ++i) { 367 lt[i] = _laserEvents[i].t - t0; 368 lv[i] = _laserEvents[i].value; 369 } 370 371 // Calculate interpolated touch y positions at each laser event. 372 std::vector<CGFloat> ly = Interpolate(lt, ft, fy); 373 374 // Labels for each laser event to denote those above/below the beam. 375 // The actual side is irrelevant, but events on the same side should have the same label. The 376 // vector will look like [0, 1, 1, 0, 0, 1, 1, 0, 0, ...]. 377 std::vector<int> sideLabels(lt.size()); 378 for (size_t i = 0; i < lt.size(); ++i) { 379 sideLabels[i] = ((i + 1) / 2) % 2; 380 } 381 382 NSTimeInterval averageBestShift = 0; 383 for (int side = 0; side < 2; ++side) { 384 std::vector<NSTimeInterval> lts = Extract(sideLabels, lt, side); 385 NSTimeInterval bestShift = FindBestShift(lts, ft, fy); 386 averageBestShift += bestShift / 2; 387 } 388 389 self.statusLabel.text = [NSString stringWithFormat:@"%.3f s", averageBestShift]; 390 391 [self reset:sender]; 392} 393@end 394