UnityAppController.mm 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. #import "UnityAppController.h"
  2. #import "UnityAppController+ViewHandling.h"
  3. #import "UnityAppController+Rendering.h"
  4. #import "iPhone_Sensors.h"
  5. #import <CoreGraphics/CoreGraphics.h>
  6. #import <QuartzCore/QuartzCore.h>
  7. #import <QuartzCore/CADisplayLink.h>
  8. #import <Availability.h>
  9. #import <AVFoundation/AVFoundation.h>
  10. #include <mach/mach_time.h>
  11. // MSAA_DEFAULT_SAMPLE_COUNT was removed
  12. // ENABLE_INTERNAL_PROFILER and related defines were moved to iPhone_Profiler.h
  13. // kFPS define for removed: you can use Application.targetFrameRate (30 fps by default)
  14. // DisplayLink is the only run loop mode now - all others were removed
  15. #include "CrashReporter.h"
  16. #include "UI/OrientationSupport.h"
  17. #include "UI/UnityView.h"
  18. #include "UI/Keyboard.h"
  19. #include "UI/UnityViewControllerBase.h"
  20. #include "Unity/InternalProfiler.h"
  21. #include "Unity/DisplayManager.h"
  22. #include "Unity/ObjCRuntime.h"
  23. #include "PluginBase/AppDelegateListener.h"
  24. #include <assert.h>
  25. #include <stdbool.h>
  26. #include <sys/types.h>
  27. #include <unistd.h>
  28. #include <sys/sysctl.h>
  29. // we assume that app delegate is never changed and we can cache it, instead of re-query UIApplication every time
  30. UnityAppController* _UnityAppController = nil;
  31. UnityAppController* GetAppController()
  32. {
  33. return _UnityAppController;
  34. }
  35. // we keep old bools around to support "old" code that might have used them
  36. bool _ios81orNewer = false, _ios82orNewer = false, _ios83orNewer = false, _ios90orNewer = false, _ios91orNewer = false;
  37. bool _ios100orNewer = false, _ios101orNewer = false, _ios102orNewer = false, _ios103orNewer = false;
  38. bool _ios110orNewer = false, _ios111orNewer = false, _ios112orNewer = false;
  39. bool _ios130orNewer = false, _ios140orNewer = false, _ios150orNewer = false, _ios160orNewer = false;
  40. // was unity rendering already inited: we should not touch rendering while this is false
  41. bool _renderingInited = false;
  42. // was unity inited: we should not touch unity api while this is false
  43. bool _unityAppReady = false;
  44. // see if there's a need to do internal player pause/resume handling
  45. //
  46. // Typically the trampoline code should manage this internally, but
  47. // there are use cases, videoplayer, plugin code, etc where the player
  48. // is paused before the internal handling comes relevant. Avoid
  49. // overriding externally managed player pause/resume handling by
  50. // caching the state
  51. bool _wasPausedExternal = false;
  52. // should we skip present on next draw: used in corner cases (like rotation) to fill both draw-buffers with some content
  53. bool _skipPresent = false;
  54. // was app "resigned active": some operations do not make sense while app is in background
  55. bool _didResignActive = false;
  56. #if UNITY_SUPPORT_ROTATION
  57. // Required to enable specific orientation for some presentation controllers: see supportedInterfaceOrientationsForWindow below for details
  58. NSInteger _forceInterfaceOrientationMask = 0;
  59. #endif
  60. @implementation UnityAppController
  61. @synthesize unityView = _unityView;
  62. @synthesize unityDisplayLink = _displayLink;
  63. @synthesize rootView = _rootView;
  64. @synthesize rootViewController = _rootController;
  65. @synthesize mainDisplay = _mainDisplay;
  66. @synthesize renderDelegate = _renderDelegate;
  67. @synthesize quitHandler = _quitHandler;
  68. #if UNITY_SUPPORT_ROTATION
  69. @synthesize interfaceOrientation = _curOrientation;
  70. #endif
  71. - (id)init
  72. {
  73. if ((self = _UnityAppController = [super init]))
  74. {
  75. // due to clang issues with generating warning for overriding deprecated methods
  76. // we will simply assert if deprecated methods are present
  77. // NB: methods table is initied at load (before this call), so it is ok to check for override
  78. NSAssert(![self respondsToSelector: @selector(createUnityViewImpl)],
  79. @"createUnityViewImpl is deprecated and will not be called. Override createUnityView"
  80. );
  81. NSAssert(![self respondsToSelector: @selector(createViewHierarchyImpl)],
  82. @"createViewHierarchyImpl is deprecated and will not be called. Override willStartWithViewController"
  83. );
  84. NSAssert(![self respondsToSelector: @selector(createViewHierarchy)],
  85. @"createViewHierarchy is deprecated and will not be implemented. Use createUI"
  86. );
  87. }
  88. return self;
  89. }
  90. - (void)setWindow:(id)object {}
  91. - (UIWindow*)window { return _window; }
  92. - (void)shouldAttachRenderDelegate {}
  93. - (void)preStartUnity {}
  94. - (void)startUnity:(UIApplication*)application
  95. {
  96. NSAssert(_unityAppReady == NO, @"[UnityAppController startUnity:] called after Unity has been initialized");
  97. UnityInitApplicationGraphics();
  98. #if !PLATFORM_VISIONOS
  99. // we make sure that first level gets correct display list and orientation
  100. [[DisplayManager Instance] updateDisplayListCacheInUnity];
  101. #endif
  102. UnityLoadApplication();
  103. Profiler_InitProfiler();
  104. [self showGameUI];
  105. [self createDisplayLink];
  106. UnitySetPlayerFocus(1);
  107. AVAudioSession* audioSession = [AVAudioSession sharedInstance];
  108. // If Unity audio is disabled, we set the category to ambient to make sure we don't mute other app's audio. We set the audio session
  109. // to active so we can get outputVolume callbacks. If Unity audio is enabled, FMOD should have already handled all of this AVAudioSession init.
  110. if (!UnityIsAudioManagerAvailableAndEnabled())
  111. {
  112. [audioSession setCategory: AVAudioSessionCategoryAmbient error: nil];
  113. [audioSession setActive: YES error: nil];
  114. }
  115. [audioSession addObserver: self forKeyPath: @"outputVolume" options: 0 context: nil];
  116. UnityUpdateMuteState([audioSession outputVolume] < 0.01f ? 1 : 0);
  117. #if UNITY_REPLAY_KIT_AVAILABLE
  118. void InitUnityReplayKit(); // Classes/Unity/UnityReplayKit.mm
  119. InitUnityReplayKit();
  120. #endif
  121. }
  122. extern "C" void UnityDestroyDisplayLink()
  123. {
  124. [GetAppController() destroyDisplayLink];
  125. }
  126. extern "C" void UnityRequestQuit()
  127. {
  128. _didResignActive = true;
  129. if (GetAppController().quitHandler)
  130. GetAppController().quitHandler();
  131. else
  132. exit(0);
  133. }
  134. extern void SensorsCleanup();
  135. extern "C" void UnityCleanupTrampoline()
  136. {
  137. // Unity view and viewController will not necessary be destroyed right after this function execution.
  138. // We need to ensure that these objects will not receive any callbacks from system during that time.
  139. [_UnityAppController window].rootViewController = nil;
  140. [[_UnityAppController unityView] removeFromSuperview];
  141. // Prevent multiple cleanups
  142. if (_UnityAppController == nil)
  143. return;
  144. [KeyboardDelegate Destroy];
  145. SensorsCleanup();
  146. Profiler_UninitProfiler();
  147. #if !PLATFORM_VISIONOS
  148. [DisplayManager Destroy];
  149. #endif
  150. UnityDestroyDisplayLink();
  151. _UnityAppController = nil;
  152. }
  153. #if UNITY_SUPPORT_ROTATION
  154. - (NSUInteger)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window
  155. {
  156. // No rootViewController is set because we are switching from one view controller to another, all orientations should be enabled
  157. if ([window rootViewController] == nil)
  158. return UIInterfaceOrientationMaskAll;
  159. // During splash screen show phase no forced orientations should be allowed.
  160. // This will prevent unwanted rotation while splash screen is on and application is not yet ready to present (Ex. Fogbugz cases: 1190428, 1269547).
  161. if (!_unityAppReady)
  162. return [_rootController supportedInterfaceOrientations];
  163. // Some presentation controllers (e.g. UIImagePickerController) require portrait orientation and will throw exception if it is not supported.
  164. // At the same time enabling all orientations by returning UIInterfaceOrientationMaskAll might cause unwanted orientation change
  165. // (e.g. when using UIActivityViewController to "share to" another application, iOS will use supportedInterfaceOrientations to possibly reorient).
  166. // So to avoid exception we are returning combination of constraints for root view controller and orientation requested by iOS.
  167. // _forceInterfaceOrientationMask is updated in willChangeStatusBarOrientation, which is called if some presentation controller insists on orientation change.
  168. return [[window rootViewController] supportedInterfaceOrientations] | _forceInterfaceOrientationMask;
  169. }
  170. - (void)application:(UIApplication*)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration
  171. {
  172. // Setting orientation mask which is requested by iOS: see supportedInterfaceOrientationsForWindow above for details
  173. _forceInterfaceOrientationMask = 1 << newStatusBarOrientation;
  174. }
  175. #endif
  176. #if UNITY_USES_REMOTE_NOTIFICATIONS
  177. - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
  178. {
  179. AppController_SendNotificationWithArg(kUnityDidRegisterForRemoteNotificationsWithDeviceToken, deviceToken);
  180. }
  181. #if !PLATFORM_TVOS
  182. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
  183. {
  184. AppController_SendNotificationWithArg(kUnityDidReceiveRemoteNotification, userInfo);
  185. if (handler)
  186. {
  187. handler(UIBackgroundFetchResultNoData);
  188. }
  189. }
  190. #endif
  191. - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
  192. {
  193. AppController_SendNotificationWithArg(kUnityDidFailToRegisterForRemoteNotificationsWithError, error);
  194. // alas people do not check remote notification error through api (which is clunky, i agree) so log here to have at least some visibility
  195. ::printf("\nFailed to register for remote notifications:\n%s\n\n", [[error localizedDescription] UTF8String]);
  196. }
  197. #endif
  198. // UIApplicationOpenURLOptionsKey was added only in ios10 sdk, while we still support ios9 sdk
  199. - (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options
  200. {
  201. id sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey], annotation = options[UIApplicationOpenURLOptionsAnnotationKey];
  202. NSMutableDictionary<NSString*, id>* notifData = [NSMutableDictionary dictionaryWithCapacity: 3];
  203. if (url)
  204. {
  205. notifData[@"url"] = url;
  206. UnitySetAbsoluteURL(url.absoluteString.UTF8String);
  207. }
  208. if (sourceApplication) notifData[@"sourceApplication"] = sourceApplication;
  209. if (annotation) notifData[@"annotation"] = annotation;
  210. AppController_SendNotificationWithArg(kUnityOnOpenURL, notifData);
  211. return YES;
  212. }
  213. - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring> > * _Nullable restorableObjects))restorationHandler
  214. {
  215. NSURL* url = userActivity.webpageURL;
  216. if (url)
  217. UnitySetAbsoluteURL(url.absoluteString.UTF8String);
  218. return YES;
  219. }
  220. - (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
  221. {
  222. AppController_SendNotificationWithArg(kUnityWillFinishLaunchingWithOptions, launchOptions);
  223. return YES;
  224. }
  225. - (UIWindowScene*)pickStartupWindowScene:(NSSet<UIScene*>*)scenes API_AVAILABLE (ios(13.0), tvos(13.0))
  226. {
  227. // if we have scene with UISceneActivationStateForegroundActive - pick it
  228. // otherwise UISceneActivationStateForegroundInactive will work
  229. // it will be the scene going into active state
  230. // if there were no active/inactive scenes (only background) we should allow background scene
  231. // this might happen in some cases with native plugins doing "things"
  232. UIWindowScene *foregroundScene = nil, *backgroundScene = nil;
  233. for (UIScene* scene in scenes)
  234. {
  235. if (![scene isKindOfClass: [UIWindowScene class]])
  236. continue;
  237. UIWindowScene* windowScene = (UIWindowScene*)scene;
  238. if (scene.activationState == UISceneActivationStateForegroundActive)
  239. return windowScene;
  240. if (scene.activationState == UISceneActivationStateForegroundInactive)
  241. foregroundScene = windowScene;
  242. else if (scene.activationState == UISceneActivationStateBackground)
  243. backgroundScene = windowScene;
  244. }
  245. return foregroundScene ? foregroundScene : backgroundScene;
  246. }
  247. - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
  248. {
  249. ::printf("-> applicationDidFinishLaunching()\n");
  250. // send notfications
  251. #if !PLATFORM_TVOS && !PLATFORM_VISIONOS
  252. if ([UIDevice currentDevice].generatesDeviceOrientationNotifications == NO)
  253. [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
  254. #endif
  255. UnityInitApplicationNoGraphics(UnityDataBundleDir());
  256. [self selectRenderingAPI];
  257. [UnityRenderingView InitializeForAPI: self.renderingAPI];
  258. #if !PLATFORM_VISIONOS
  259. if (@available(iOS 13, tvOS 13, *))
  260. _window = [[UIWindow alloc] initWithWindowScene: [self pickStartupWindowScene: application.connectedScenes]];
  261. else
  262. _window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
  263. #else
  264. _window = [[UIWindow alloc] init];
  265. #endif
  266. _unityView = [self createUnityView];
  267. [DisplayManager Initialize];
  268. _mainDisplay = [DisplayManager Instance].mainDisplay;
  269. [_mainDisplay createWithWindow: _window andView: _unityView];
  270. [self createUI];
  271. [self preStartUnity];
  272. // if you wont use keyboard you may comment it out at save some memory
  273. [KeyboardDelegate Initialize];
  274. [self startUnity: application];
  275. return YES;
  276. }
  277. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
  278. {
  279. if ([keyPath isEqual: @"outputVolume"])
  280. {
  281. UnityUpdateMuteState([[AVAudioSession sharedInstance] outputVolume] < 0.01f ? 1 : 0);
  282. }
  283. }
  284. - (void)applicationDidEnterBackground:(UIApplication*)application
  285. {
  286. ::printf("-> applicationDidEnterBackground()\n");
  287. }
  288. - (void)applicationWillEnterForeground:(UIApplication*)application
  289. {
  290. ::printf("-> applicationWillEnterForeground()\n");
  291. // applicationWillEnterForeground: might sometimes arrive *before* actually initing unity (e.g. locking on startup)
  292. if (_unityAppReady)
  293. {
  294. // if we were showing video before going to background - the view size may be changed while we are in background
  295. [GetAppController().unityView recreateRenderingSurfaceIfNeeded];
  296. }
  297. }
  298. - (void)applicationDidBecomeActive:(UIApplication*)application
  299. {
  300. ::printf("-> applicationDidBecomeActive()\n");
  301. [self removeSnapshotViewController];
  302. if (_unityAppReady)
  303. {
  304. // Pause/unpause is handled by repaint if CompositorLayer is in use
  305. if (_LayerRenderer == nil && UnityIsPaused() && _wasPausedExternal == false)
  306. {
  307. UnityWillResume();
  308. UnityPause(0);
  309. }
  310. if (_wasPausedExternal)
  311. {
  312. if (UnityIsFullScreenPlaying())
  313. TryResumeFullScreenVideo();
  314. }
  315. // need to do this with delay because FMOD restarts audio in AVAudioSessionInterruptionNotification handler
  316. [self performSelector: @selector(updateUnityAudioOutput) withObject: nil afterDelay: 0.1];
  317. UnitySetPlayerFocus(1);
  318. }
  319. _didResignActive = false;
  320. }
  321. - (void)updateUnityAudioOutput
  322. {
  323. UnityUpdateMuteState([[AVAudioSession sharedInstance] outputVolume] < 0.01f ? 1 : 0);
  324. }
  325. - (void)addSnapshotViewController
  326. {
  327. if (!_didResignActive || self->_snapshotViewController)
  328. {
  329. return;
  330. }
  331. UIView* snapshotView = [self createSnapshotView];
  332. if (snapshotView != nil)
  333. {
  334. UIViewController* snapshotViewController = [AllocUnityViewController() init];
  335. snapshotViewController.modalPresentationStyle = UIModalPresentationFullScreen;
  336. snapshotViewController.view = snapshotView;
  337. [self->_rootController presentViewController: snapshotViewController animated: false completion: nil];
  338. self->_snapshotViewController = snapshotViewController;
  339. }
  340. }
  341. - (void)removeSnapshotViewController
  342. {
  343. // do this on the main queue async so that if we try to create one
  344. // and remove in the same frame, this always happens after in the same queue
  345. dispatch_async(dispatch_get_main_queue(), ^{
  346. if (self->_snapshotViewController)
  347. {
  348. // we've got a view on top of the snapshot view (3rd party plugin/social media login etc).
  349. if (self->_snapshotViewController.presentedViewController)
  350. {
  351. [self performSelector: @selector(removeSnapshotViewController) withObject: nil afterDelay: 0.05];
  352. return;
  353. }
  354. [self->_snapshotViewController dismissViewControllerAnimated: NO completion: nil];
  355. self->_snapshotViewController = nil;
  356. // Make sure that the keyboard input field regains focus after the application becomes active.
  357. [[KeyboardDelegate Instance] becomeFirstResponder];
  358. }
  359. });
  360. }
  361. - (void)applicationWillResignActive:(UIApplication*)application
  362. {
  363. ::printf("-> applicationWillResignActive()\n");
  364. if (_unityAppReady)
  365. {
  366. UnitySetPlayerFocus(0);
  367. // signal unity that the frame rendering have ended
  368. // as we will not get the callback from the display link current frame
  369. UnityDisplayLinkCallback(0);
  370. _wasPausedExternal = UnityIsPaused();
  371. // Pause/unpause is handled by repaint if CompositorLayer is in use
  372. if (_LayerRenderer == nil && _wasPausedExternal == false)
  373. {
  374. // Pause Unity only if we don't need special background processing
  375. // otherwise batched player loop can be called to run user scripts.
  376. if (!UnityGetUseCustomAppBackgroundBehavior())
  377. {
  378. #if UNITY_SNAPSHOT_VIEW_ON_APPLICATION_PAUSE
  379. // Force player to do one more frame, so scripts get a chance to render custom screen for minimized app in task manager.
  380. // NB: UnityWillPause will schedule OnApplicationPause message, which will be sent normally inside repaint (unity player loop)
  381. // NB: We will actually pause after the loop (when calling UnityPause).
  382. UnityWillPause();
  383. [self repaint];
  384. UnityWaitForFrame();
  385. [self addSnapshotViewController];
  386. #endif
  387. UnityPause(1);
  388. }
  389. }
  390. }
  391. _didResignActive = true;
  392. }
  393. - (void)applicationWillTerminate:(UIApplication*)application
  394. {
  395. ::printf("-> applicationWillTerminate()\n");
  396. // Only clean up if Unity has finished initializing, else the clean up process will crash,
  397. // this happens if the app is force closed immediately after opening it.
  398. if (_unityAppReady)
  399. {
  400. UnityCleanup();
  401. UnityCleanupTrampoline();
  402. }
  403. }
  404. - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)())completionHandler
  405. {
  406. NSDictionary* arg = @{identifier: completionHandler};
  407. AppController_SendNotificationWithArg(kUnityHandleEventsForBackgroundURLSession, arg);
  408. }
  409. @end
  410. void AppController_SendNotification(NSString* name)
  411. {
  412. [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController()];
  413. }
  414. void AppController_SendNotificationWithArg(NSString* name, id arg)
  415. {
  416. [[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController() userInfo: arg];
  417. }
  418. void AppController_SendUnityViewControllerNotification(NSString* name)
  419. {
  420. [[NSNotificationCenter defaultCenter] postNotificationName: name object: UnityGetGLViewController()];
  421. }
  422. extern "C" UIWindow* UnityGetMainWindow() { return GetAppController().mainDisplay.window; }
  423. extern "C" UIViewController* UnityGetGLViewController() { return GetAppController().rootViewController; }
  424. extern "C" UnityView* UnityGetUnityView() { return GetAppController().unityView; }
  425. extern "C" UIView* UnityGetGLView() { return UnityGetUnityView(); }
  426. extern "C" ScreenOrientation UnityCurrentOrientation() { return GetAppController().unityView.contentOrientation; }
  427. bool LogToNSLogHandler(LogType logType, const char* log, va_list list)
  428. {
  429. NSLogv([NSString stringWithUTF8String: log], list);
  430. return true;
  431. }
  432. static void AddNewAPIImplIfNeeded();
  433. // From https://stackoverflow.com/questions/4744826/detecting-if-ios-app-is-run-in-debugger
  434. static bool isDebuggerAttachedToConsole(void)
  435. // Returns true if the current process is being debugged (either
  436. // running under the debugger or has a debugger attached post facto).
  437. {
  438. int junk;
  439. int mib[4];
  440. struct kinfo_proc info;
  441. size_t size;
  442. // Initialize the flags so that, if sysctl fails for some bizarre
  443. // reason, we get a predictable result.
  444. info.kp_proc.p_flag = 0;
  445. // Initialize mib, which tells sysctl the info we want, in this case
  446. // we're looking for information about a specific process ID.
  447. mib[0] = CTL_KERN;
  448. mib[1] = KERN_PROC;
  449. mib[2] = KERN_PROC_PID;
  450. mib[3] = getpid();
  451. // Call sysctl.
  452. size = sizeof(info);
  453. junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
  454. assert(junk == 0);
  455. // We're being debugged if the P_TRACED flag is set.
  456. return ((info.kp_proc.p_flag & P_TRACED) != 0);
  457. }
  458. void UnityInitTrampoline()
  459. {
  460. InitCrashHandling();
  461. NSString* version = [[UIDevice currentDevice] systemVersion];
  462. #define CHECK_VER(s) [version compare: s options: NSNumericSearch] != NSOrderedAscending
  463. _ios81orNewer = CHECK_VER(@"8.1"); _ios82orNewer = CHECK_VER(@"8.2"); _ios83orNewer = CHECK_VER(@"8.3");
  464. _ios90orNewer = CHECK_VER(@"9.0"); _ios91orNewer = CHECK_VER(@"9.1");
  465. _ios100orNewer = CHECK_VER(@"10.0"); _ios101orNewer = CHECK_VER(@"10.1"); _ios102orNewer = CHECK_VER(@"10.2"); _ios103orNewer = CHECK_VER(@"10.3");
  466. _ios110orNewer = CHECK_VER(@"11.0"); _ios111orNewer = CHECK_VER(@"11.1"); _ios112orNewer = CHECK_VER(@"11.2");
  467. _ios130orNewer = CHECK_VER(@"13.0"); _ios140orNewer = CHECK_VER(@"14.0"); _ios150orNewer = CHECK_VER(@"15.0");
  468. _ios160orNewer = CHECK_VER(@"16.0");
  469. #undef CHECK_VER
  470. AddNewAPIImplIfNeeded();
  471. #if !TARGET_IPHONE_SIMULATOR
  472. // Use NSLog logging if a debugger is not attached, otherwise we write to stdout.
  473. if (!isDebuggerAttachedToConsole())
  474. UnitySetLogEntryHandler(LogToNSLogHandler);
  475. #endif
  476. }
  477. extern "C" bool UnityiOS81orNewer() { return _ios81orNewer; }
  478. extern "C" bool UnityiOS82orNewer() { return _ios82orNewer; }
  479. extern "C" bool UnityiOS90orNewer() { return _ios90orNewer; }
  480. extern "C" bool UnityiOS91orNewer() { return _ios91orNewer; }
  481. extern "C" bool UnityiOS100orNewer() { return _ios100orNewer; }
  482. extern "C" bool UnityiOS101orNewer() { return _ios101orNewer; }
  483. extern "C" bool UnityiOS102orNewer() { return _ios102orNewer; }
  484. extern "C" bool UnityiOS103orNewer() { return _ios103orNewer; }
  485. extern "C" bool UnityiOS110orNewer() { return _ios110orNewer; }
  486. extern "C" bool UnityiOS111orNewer() { return _ios111orNewer; }
  487. extern "C" bool UnityiOS112orNewer() { return _ios112orNewer; }
  488. extern "C" bool UnityiOS130orNewer() { return _ios130orNewer; }
  489. extern "C" bool UnityiOS140orNewer() { return _ios140orNewer; }
  490. extern "C" bool UnityiOS150orNewer() { return _ios150orNewer; }
  491. extern "C" bool UnityiOS160orNewer() { return _ios160orNewer; }
  492. // sometimes apple adds new api with obvious fallback on older ios.
  493. // in that case we simply add these functions ourselves to simplify code
  494. static void AddNewAPIImplIfNeeded()
  495. {
  496. #if !PLATFORM_VISIONOS
  497. if (![[UIScreen class] instancesRespondToSelector: @selector(maximumFramesPerSecond)])
  498. {
  499. IMP UIScreen_MaximumFramesPerSecond_IMP = imp_implementationWithBlock(^NSInteger(id _self) {
  500. return 60;
  501. });
  502. class_replaceMethod([UIScreen class], @selector(maximumFramesPerSecond), UIScreen_MaximumFramesPerSecond_IMP, UIScreen_maximumFramesPerSecond_Enc);
  503. }
  504. if (![[UIView class] instancesRespondToSelector: @selector(safeAreaInsets)])
  505. {
  506. IMP UIView_SafeAreaInsets_IMP = imp_implementationWithBlock(^UIEdgeInsets(id _self) {
  507. return UIEdgeInsetsZero;
  508. });
  509. class_replaceMethod([UIView class], @selector(safeAreaInsets), UIView_SafeAreaInsets_IMP, UIView_safeAreaInsets_Enc);
  510. }
  511. #endif
  512. }