mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
cmd/ebitenmobile: bug fix: accessing a view
property caused deadlock on iOS
This change delays the initialization of the view until viewDidLoad is called AND mobile.SetGame is called. Closes #2768
This commit is contained in:
parent
6016c934f5
commit
3608aac5ca
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
#import "Ebitenmobileview.objc.h"
|
#import "Ebitenmobileview.objc.h"
|
||||||
|
|
||||||
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester>
|
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester, EbitenmobileviewSetGameNotifier>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation {{.PrefixUpper}}EbitenViewController {
|
@implementation {{.PrefixUpper}}EbitenViewController {
|
||||||
@ -32,6 +32,28 @@
|
|||||||
CADisplayLink* displayLink_;
|
CADisplayLink* displayLink_;
|
||||||
bool explicitRendering_;
|
bool explicitRendering_;
|
||||||
NSThread* renderThread_;
|
NSThread* renderThread_;
|
||||||
|
bool viewDidLoad_;
|
||||||
|
bool gameSet_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithNibName:(NSString *)nibNameOrNil
|
||||||
|
bundle:(NSBundle *)nibBundleOrNil {
|
||||||
|
self = [super initWithNibName:nibNameOrNil
|
||||||
|
bundle:nibBundleOrNil];
|
||||||
|
if (self) {
|
||||||
|
EbitenmobileviewSetSetGameNotifier(self);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithCoder:(NSCoder *)coder {
|
||||||
|
// Though initWithCoder might not be a designated initializer, this should be overwritten.
|
||||||
|
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/codingobjects.html
|
||||||
|
self = [super initWithCoder:coder];
|
||||||
|
if (self) {
|
||||||
|
EbitenmobileviewSetSetGameNotifier(self);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIView*)metalView {
|
- (UIView*)metalView {
|
||||||
@ -53,6 +75,18 @@
|
|||||||
- (void)viewDidLoad {
|
- (void)viewDidLoad {
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
viewDidLoad_ = true;
|
||||||
|
if (viewDidLoad_ && gameSet_) {
|
||||||
|
[self initView];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)initView {
|
||||||
|
// initView must be called only when viewDidLoad_, and gameSet_ are true i.e. mobile.SetGame is called.
|
||||||
|
// Or, EbitenmobileviewIsGL causes a dead lock (#2768).
|
||||||
|
// A game is requried to determine a graphics driver, and EbitenmobileviewIsGL cannot return a value without a game.
|
||||||
|
NSAssert(viewDidLoad_ && gameSet_, @"viewDidLoad must be called and a game must be set at initView");
|
||||||
|
|
||||||
if (!started_) {
|
if (!started_) {
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
active_ = true;
|
active_ = true;
|
||||||
@ -122,6 +156,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillLayoutSubviews {
|
- (void)viewWillLayoutSubviews {
|
||||||
|
if (!started_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NSError* err = nil;
|
NSError* err = nil;
|
||||||
BOOL isGL = NO;
|
BOOL isGL = NO;
|
||||||
EbitenmobileviewIsGL(&isGL, &err);
|
EbitenmobileviewIsGL(&isGL, &err);
|
||||||
@ -143,6 +181,11 @@
|
|||||||
|
|
||||||
- (void)viewDidLayoutSubviews {
|
- (void)viewDidLayoutSubviews {
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
|
|
||||||
|
if (!started_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CGRect viewRect = [[self view] frame];
|
CGRect viewRect = [[self view] frame];
|
||||||
|
|
||||||
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
|
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
|
||||||
@ -217,6 +260,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateTouches:(NSSet*)touches {
|
- (void)updateTouches:(NSSet*)touches {
|
||||||
|
if (!started_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NSError* err = nil;
|
NSError* err = nil;
|
||||||
BOOL isGL = NO;
|
BOOL isGL = NO;
|
||||||
EbitenmobileviewIsGL(&isGL, &err);
|
EbitenmobileviewIsGL(&isGL, &err);
|
||||||
@ -260,7 +307,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)suspendGame {
|
- (void)suspendGame {
|
||||||
NSAssert(started_, @"suspendGame must not be called before viewDidLoad is called");
|
if (!started_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
active_ = false;
|
active_ = false;
|
||||||
@ -274,7 +323,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)resumeGame {
|
- (void)resumeGame {
|
||||||
NSAssert(started_, @"resumeGame must not be called before viewDidLoad is called");
|
if (!started_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
active_ = true;
|
active_ = true;
|
||||||
@ -306,4 +357,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)notifySetGame {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
gameSet_ = true;
|
||||||
|
if (viewDidLoad_ && gameSet_) {
|
||||||
|
[self initView];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -27,7 +27,7 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
|
"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
|
||||||
@ -35,18 +35,49 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SetGameNotifier interface {
|
||||||
|
NotifySetGame()
|
||||||
|
}
|
||||||
|
|
||||||
var theState state
|
var theState state
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
running int32
|
running bool
|
||||||
|
setGameNotifier SetGameNotifier
|
||||||
|
|
||||||
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) isRunning() bool {
|
func (s *state) isRunning() bool {
|
||||||
return atomic.LoadInt32(&s.running) != 0
|
s.m.Lock()
|
||||||
|
defer s.m.Unlock()
|
||||||
|
return s.running
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) run() {
|
func (s *state) run() {
|
||||||
atomic.StoreInt32(&s.running, 1)
|
s.m.Lock()
|
||||||
|
s.running = true
|
||||||
|
n := s.setGameNotifier
|
||||||
|
s.setGameNotifier = nil
|
||||||
|
s.m.Unlock()
|
||||||
|
|
||||||
|
if n != nil {
|
||||||
|
n.NotifySetGame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) setSetGameNotifier(setGameNotifier SetGameNotifier) {
|
||||||
|
s.m.Lock()
|
||||||
|
r := s.running
|
||||||
|
if !r {
|
||||||
|
s.setGameNotifier = setGameNotifier
|
||||||
|
}
|
||||||
|
s.m.Unlock()
|
||||||
|
|
||||||
|
// If SetGame is already called, notify this immediately.
|
||||||
|
if r {
|
||||||
|
setGameNotifier.NotifySetGame()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetGame(game ebiten.Game, options *ebiten.RunGameOptions) {
|
func SetGame(game ebiten.Game, options *ebiten.RunGameOptions) {
|
||||||
@ -99,3 +130,7 @@ type RenderRequester interface {
|
|||||||
func SetRenderRequester(renderRequester RenderRequester) {
|
func SetRenderRequester(renderRequester RenderRequester) {
|
||||||
ui.Get().SetRenderRequester(renderRequester)
|
ui.Get().SetRenderRequester(renderRequester)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSetGameNotifier(setGameNotifier SetGameNotifier) {
|
||||||
|
theState.setSetGameNotifier(setGameNotifier)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user