From 7d146fb70b4a362525e33423a453350ace47db47 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 15 Nov 2022 01:34:25 +0900 Subject: [PATCH] internal/ui: bug fix: IsGL / SetUIView can be called before initialization is done The functions in the package `mobile/ebitenmobileview` could be invoked from EbitenViewController even before the graphics driver initialization is done in theory. This change fixes this issue by waiting the initialization by channels. Also, this change adds error handlings at these functions. Closes #2455 --- .../_files/EbitenViewController.m | 71 ++++++++++++++++--- internal/ui/ui_ios.go | 35 +++++++-- internal/ui/ui_mobile.go | 9 ++- mobile/ebitenmobileview/view_ios.go | 6 +- 4 files changed, 100 insertions(+), 21 deletions(-) diff --git a/cmd/ebitenmobile/_files/EbitenViewController.m b/cmd/ebitenmobile/_files/EbitenViewController.m index 8ca0b77f7..69cdfc4eb 100644 --- a/cmd/ebitenmobile/_files/EbitenViewController.m +++ b/cmd/ebitenmobile/_files/EbitenViewController.m @@ -59,7 +59,18 @@ started_ = true; } - if (EbitenmobileviewIsGL()) { + NSError* err = nil; + BOOL isGL = NO; + EbitenmobileviewIsGL(&isGL, &err); + if (err != nil) { + [self onErrorOnGameUpdate:err]; + @synchronized(self) { + error_ = true; + } + return; + } + + if (isGL) { self.glkView.delegate = (id)(self); [self.view addSubview: self.glkView]; @@ -69,7 +80,14 @@ [EAGLContext setCurrentContext:context]; } else { [self.view addSubview: self.metalView]; - EbitenmobileviewSetUIView((uintptr_t)(self.metalView)); + EbitenmobileviewSetUIView((uintptr_t)(self.metalView), &err); + if (err != nil) { + [self onErrorOnGameUpdate:err]; + @synchronized(self) { + error_ = true; + } + return; + } } displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)]; @@ -78,8 +96,19 @@ } - (void)viewWillLayoutSubviews { + NSError* err = nil; + BOOL isGL = NO; + EbitenmobileviewIsGL(&isGL, &err); + if (err != nil) { + [self onErrorOnGameUpdate:err]; + @synchronized(self) { + error_ = true; + } + return; + } + CGRect viewRect = [[self view] frame]; - if (EbitenmobileviewIsGL()) { + if (isGL) { [[self glkView] setFrame:viewRect]; } else { [[self metalView] setFrame:viewRect]; @@ -106,7 +135,18 @@ } } - if (EbitenmobileviewIsGL()) { + NSError* err = nil; + BOOL isGL = NO; + EbitenmobileviewIsGL(&isGL, &err); + if (err != nil) { + [self onErrorOnGameUpdate:err]; + @synchronized(self) { + error_ = true; + } + return; + } + + if (isGL) { [[self glkView] setNeedsDisplay]; } else { [self updateEbiten]; @@ -124,8 +164,10 @@ } - (void)updateEbiten { - if (error_) { - return; + @synchronized(self) { + if (error_) { + return; + } } NSError* err = nil; @@ -134,7 +176,9 @@ [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:) withObject:err waitUntilDone:NO]; - error_ = true; + @synchronized(self) { + error_ = true; + } } } @@ -143,8 +187,19 @@ } - (void)updateTouches:(NSSet*)touches { + NSError* err = nil; + BOOL isGL = NO; + EbitenmobileviewIsGL(&isGL, &err); + if (err != nil) { + [self onErrorOnGameUpdate:err]; + @synchronized(self) { + error_ = true; + } + return; + } + for (UITouch* touch in touches) { - if (EbitenmobileviewIsGL()) { + if (isGL) { if (touch.view != [self glkView]) { continue; } diff --git a/internal/ui/ui_ios.go b/internal/ui/ui_ios.go index 32ec17b5e..497a5d25b 100644 --- a/internal/ui/ui_ios.go +++ b/internal/ui/ui_ios.go @@ -55,13 +55,34 @@ func (g *graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) return metal.NewGraphics() } -func SetUIView(uiview uintptr) { - // This function should be called only when the graphics library is Metal. - if g, ok := theUI.graphicsDriver.(interface{ SetUIView(uintptr) }); ok { - g.SetUIView(uiview) - } +func SetUIView(uiview uintptr) error { + return theUI.setUIView(uiview) } -func IsGL() bool { - return theUI.graphicsDriver.IsGL() +func IsGL() (bool, error) { + return theUI.isGL() +} + +func (u *userInterfaceImpl) setUIView(uiview uintptr) error { + select { + case err := <-u.errCh: + return err + case <-u.graphicsDriverInitCh: + } + + // This function should be called only when the graphics library is Metal. + if g, ok := u.graphicsDriver.(interface{ SetUIView(uintptr) }); ok { + g.SetUIView(uiview) + } + return nil +} + +func (u *userInterfaceImpl) isGL() (bool, error) { + select { + case err := <-u.errCh: + return false, err + case <-u.graphicsDriverInitCh: + } + + return u.graphicsDriver.IsGL(), nil } diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index 71aab8043..a5fb6cf27 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -52,8 +52,9 @@ var ( func init() { theUI.userInterfaceImpl = userInterfaceImpl{ - foreground: 1, - errCh: make(chan error), + foreground: 1, + graphicsDriverInitCh: make(chan struct{}), + errCh: make(chan error), // Give a default outside size so that the game can start without initializing them. outsideWidth: 640, @@ -90,7 +91,8 @@ func (u *userInterfaceImpl) Update() error { } type userInterfaceImpl struct { - graphicsDriver graphicsdriver.Graphics + graphicsDriver graphicsdriver.Graphics + graphicsDriverInitCh chan struct{} outsideWidth float64 outsideHeight float64 @@ -287,6 +289,7 @@ func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) { return err } u.graphicsDriver = g + close(u.graphicsDriverInitCh) // If gomobile-build is used, wait for the outside size fixed. if u.setGBuildSizeCh != nil { diff --git a/mobile/ebitenmobileview/view_ios.go b/mobile/ebitenmobileview/view_ios.go index 3e6f1d9af..835f7ada3 100644 --- a/mobile/ebitenmobileview/view_ios.go +++ b/mobile/ebitenmobileview/view_ios.go @@ -18,10 +18,10 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/ui" ) -func SetUIView(uiview int64) { - ui.SetUIView(uintptr(uiview)) +func SetUIView(uiview int64) error { + return ui.SetUIView(uintptr(uiview)) } -func IsGL() bool { +func IsGL() (bool, error) { return ui.IsGL() }