暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

UnityAppController.mm 27KB

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