1
0

EbitenViewController.m 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. // Copyright 2022 The Ebitengine Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <TargetConditionals.h>
  15. #import <stdint.h>
  16. #import <UIKit/UIKit.h>
  17. #import <GLKit/GLKit.h>
  18. #import "Ebitenmobileview.objc.h"
  19. @interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderer, EbitenmobileviewSetGameNotifier>
  20. @end
  21. @implementation {{.PrefixUpper}}EbitenViewController {
  22. UIView* metalView_;
  23. GLKView* glkView_;
  24. bool started_;
  25. bool active_;
  26. bool error_;
  27. CADisplayLink* displayLink_;
  28. bool explicitRendering_;
  29. NSThread* renderThread_;
  30. bool viewDidLoad_;
  31. bool gameSet_;
  32. }
  33. - (id)initWithNibName:(NSString *)nibNameOrNil
  34. bundle:(NSBundle *)nibBundleOrNil {
  35. self = [super initWithNibName:nibNameOrNil
  36. bundle:nibBundleOrNil];
  37. if (self) {
  38. EbitenmobileviewSetSetGameNotifier(self);
  39. }
  40. return self;
  41. }
  42. - (id)initWithCoder:(NSCoder *)coder {
  43. // Though initWithCoder might not be a designated initializer, this should be overwritten.
  44. // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/codingobjects.html
  45. self = [super initWithCoder:coder];
  46. if (self) {
  47. EbitenmobileviewSetSetGameNotifier(self);
  48. }
  49. return self;
  50. }
  51. - (UIView*)metalView {
  52. if (!metalView_) {
  53. metalView_ = [[UIView alloc] init];
  54. metalView_.multipleTouchEnabled = YES;
  55. }
  56. return metalView_;
  57. }
  58. - (GLKView*)glkView {
  59. if (!glkView_) {
  60. glkView_ = [[GLKView alloc] init];
  61. glkView_.multipleTouchEnabled = YES;
  62. }
  63. return glkView_;
  64. }
  65. - (void)viewDidLoad {
  66. [super viewDidLoad];
  67. viewDidLoad_ = true;
  68. if (viewDidLoad_ && gameSet_) {
  69. [self initView];
  70. }
  71. }
  72. - (void)initView {
  73. // initView must be called only when viewDidLoad_, and gameSet_ are true i.e. mobile.SetGame is called.
  74. // Or, EbitenmobileviewIsGL causes a dead lock (#2768).
  75. // A game is required to determine a graphics driver, and EbitenmobileviewIsGL cannot return a value without a game.
  76. NSAssert(viewDidLoad_ && gameSet_, @"viewDidLoad must be called and a game must be set at initView");
  77. if (!started_) {
  78. @synchronized(self) {
  79. active_ = true;
  80. }
  81. started_ = true;
  82. }
  83. NSError* err = nil;
  84. BOOL isGL = NO;
  85. EbitenmobileviewIsGL(&isGL, &err);
  86. if (err != nil) {
  87. [self onErrorOnGameUpdate:err];
  88. @synchronized(self) {
  89. error_ = true;
  90. }
  91. return;
  92. }
  93. if (isGL) {
  94. self.glkView.delegate = (id<GLKViewDelegate>)(self);
  95. [self.view addSubview: self.glkView];
  96. } else {
  97. [self.view addSubview: self.metalView];
  98. EbitenmobileviewSetUIView((uintptr_t)(self.metalView), &err);
  99. if (err != nil) {
  100. [self onErrorOnGameUpdate:err];
  101. @synchronized(self) {
  102. error_ = true;
  103. }
  104. return;
  105. }
  106. }
  107. renderThread_ = [[NSThread alloc] initWithTarget:self
  108. selector:@selector(initRenderer)
  109. object:nil];
  110. [renderThread_ start];
  111. }
  112. - (void)initRenderer {
  113. NSError* err = nil;
  114. BOOL isGL = NO;
  115. EbitenmobileviewIsGL(&isGL, &err);
  116. if (err != nil) {
  117. [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
  118. withObject:err
  119. waitUntilDone:NO];
  120. @synchronized(self) {
  121. error_ = true;
  122. }
  123. return;
  124. }
  125. if (isGL) {
  126. EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
  127. [self glkView].context = context;
  128. [EAGLContext setCurrentContext:context];
  129. }
  130. displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
  131. [displayLink_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  132. EbitenmobileviewSetRenderer(self);
  133. // Run the loop. This will never return.
  134. [[NSRunLoop currentRunLoop] run];
  135. }
  136. - (void)viewWillLayoutSubviews {
  137. if (!started_) {
  138. return;
  139. }
  140. NSError* err = nil;
  141. BOOL isGL = NO;
  142. EbitenmobileviewIsGL(&isGL, &err);
  143. if (err != nil) {
  144. [self onErrorOnGameUpdate:err];
  145. @synchronized(self) {
  146. error_ = true;
  147. }
  148. return;
  149. }
  150. CGRect viewRect = [[self view] frame];
  151. if (isGL) {
  152. [[self glkView] setFrame:viewRect];
  153. } else {
  154. [[self metalView] setFrame:viewRect];
  155. }
  156. }
  157. - (void)viewDidLayoutSubviews {
  158. [super viewDidLayoutSubviews];
  159. if (!started_) {
  160. return;
  161. }
  162. CGRect viewRect = [[self view] frame];
  163. EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
  164. }
  165. - (void)didReceiveMemoryWarning {
  166. [super didReceiveMemoryWarning];
  167. // Dispose of any resources that can be recreated.
  168. // TODO: Notify this to Go world?
  169. }
  170. - (void)drawFrame{
  171. @synchronized(self) {
  172. if (!active_) {
  173. return;
  174. }
  175. }
  176. NSError* err = nil;
  177. BOOL isGL = NO;
  178. EbitenmobileviewIsGL(&isGL, &err);
  179. if (err != nil) {
  180. [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
  181. withObject:err
  182. waitUntilDone:NO];
  183. @synchronized(self) {
  184. error_ = true;
  185. }
  186. return;
  187. }
  188. if (isGL) {
  189. dispatch_async(dispatch_get_main_queue(), ^{
  190. [[self glkView] setNeedsDisplay];
  191. });
  192. } else {
  193. [self updateEbiten];
  194. }
  195. @synchronized(self) {
  196. if (explicitRendering_) {
  197. [displayLink_ setPaused:YES];
  198. }
  199. }
  200. }
  201. - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
  202. [self updateEbiten];
  203. }
  204. - (void)updateEbiten {
  205. @synchronized(self) {
  206. if (error_) {
  207. return;
  208. }
  209. }
  210. NSError* err = nil;
  211. EbitenmobileviewUpdate(&err);
  212. if (err != nil) {
  213. [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
  214. withObject:err
  215. waitUntilDone:NO];
  216. @synchronized(self) {
  217. error_ = true;
  218. }
  219. }
  220. }
  221. - (void)onErrorOnGameUpdate:(NSError*)err {
  222. NSLog(@"Error: %@", err);
  223. }
  224. - (void)updateTouches:(NSSet*)touches {
  225. if (!started_) {
  226. return;
  227. }
  228. NSError* err = nil;
  229. BOOL isGL = NO;
  230. EbitenmobileviewIsGL(&isGL, &err);
  231. if (err != nil) {
  232. [self onErrorOnGameUpdate:err];
  233. @synchronized(self) {
  234. error_ = true;
  235. }
  236. return;
  237. }
  238. for (UITouch* touch in touches) {
  239. if (isGL) {
  240. if (touch.view != [self glkView]) {
  241. continue;
  242. }
  243. } else {
  244. if (touch.view != [self metalView]) {
  245. continue;
  246. }
  247. }
  248. CGPoint location = [touch locationInView:touch.view];
  249. EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);
  250. }
  251. }
  252. - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  253. [self updateTouches:touches];
  254. }
  255. - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
  256. [self updateTouches:touches];
  257. }
  258. - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
  259. [self updateTouches:touches];
  260. }
  261. - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
  262. [self updateTouches:touches];
  263. }
  264. - (void)updatePresses:(NSSet<UIPress *> *)presses {
  265. if (!started_) {
  266. return;
  267. }
  268. if (@available(iOS 13.4, *)) {
  269. // Note: before iOS 13.4, this just can return UIPressType, which is
  270. // insufficient for games.
  271. for (UIPress *press in presses) {
  272. UIKey *key = press.key;
  273. if (key == nil) {
  274. continue;
  275. }
  276. EbitenmobileviewUpdatePressesOnIOS(press.phase, key.keyCode, key.characters);
  277. }
  278. }
  279. }
  280. - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
  281. [self updatePresses:presses];
  282. }
  283. - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
  284. [self updatePresses:presses];
  285. }
  286. - (void)suspendGame {
  287. if (!started_) {
  288. return;
  289. }
  290. @synchronized(self) {
  291. active_ = false;
  292. }
  293. NSError* err = nil;
  294. EbitenmobileviewSuspend(&err);
  295. if (err != nil) {
  296. [self onErrorOnGameUpdate:err];
  297. }
  298. }
  299. - (void)resumeGame {
  300. if (!started_) {
  301. return;
  302. }
  303. @synchronized(self) {
  304. active_ = true;
  305. }
  306. NSError* err = nil;
  307. EbitenmobileviewResume(&err);
  308. if (err != nil) {
  309. [self onErrorOnGameUpdate:err];
  310. }
  311. }
  312. - (void)setExplicitRenderingMode:(BOOL)explicitRendering {
  313. @synchronized(self) {
  314. explicitRendering_ = explicitRendering;
  315. if (explicitRendering_) {
  316. [displayLink_ setPaused:YES];
  317. }
  318. }
  319. }
  320. - (void)requestRenderIfNeeded {
  321. @synchronized(self) {
  322. if (explicitRendering_) {
  323. // Resume the callback temporarily.
  324. // This is paused again soon in drawFrame.
  325. [displayLink_ setPaused:NO];
  326. }
  327. }
  328. }
  329. - (void)notifySetGame {
  330. dispatch_async(dispatch_get_main_queue(), ^{
  331. gameSet_ = true;
  332. if (viewDidLoad_ && gameSet_) {
  333. [self initView];
  334. }
  335. });
  336. }
  337. @end