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
This commit is contained in:
Hajime Hoshi 2022-11-15 01:34:25 +09:00
parent f4e63602d3
commit 7d146fb70b
4 changed files with 100 additions and 21 deletions

View File

@ -59,7 +59,18 @@
started_ = true; 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<GLKViewDelegate>)(self); self.glkView.delegate = (id<GLKViewDelegate>)(self);
[self.view addSubview: self.glkView]; [self.view addSubview: self.glkView];
@ -69,7 +80,14 @@
[EAGLContext setCurrentContext:context]; [EAGLContext setCurrentContext:context];
} else { } else {
[self.view addSubview: self.metalView]; [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)]; displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
@ -78,8 +96,19 @@
} }
- (void)viewWillLayoutSubviews { - (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]; CGRect viewRect = [[self view] frame];
if (EbitenmobileviewIsGL()) { if (isGL) {
[[self glkView] setFrame:viewRect]; [[self glkView] setFrame:viewRect];
} else { } else {
[[self metalView] setFrame:viewRect]; [[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]; [[self glkView] setNeedsDisplay];
} else { } else {
[self updateEbiten]; [self updateEbiten];
@ -124,8 +164,10 @@
} }
- (void)updateEbiten { - (void)updateEbiten {
if (error_) { @synchronized(self) {
return; if (error_) {
return;
}
} }
NSError* err = nil; NSError* err = nil;
@ -134,7 +176,9 @@
[self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:) [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
withObject:err withObject:err
waitUntilDone:NO]; waitUntilDone:NO];
error_ = true; @synchronized(self) {
error_ = true;
}
} }
} }
@ -143,8 +187,19 @@
} }
- (void)updateTouches:(NSSet*)touches { - (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) { for (UITouch* touch in touches) {
if (EbitenmobileviewIsGL()) { if (isGL) {
if (touch.view != [self glkView]) { if (touch.view != [self glkView]) {
continue; continue;
} }

View File

@ -55,13 +55,34 @@ func (g *graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error)
return metal.NewGraphics() return metal.NewGraphics()
} }
func SetUIView(uiview uintptr) { func SetUIView(uiview uintptr) error {
// This function should be called only when the graphics library is Metal. return theUI.setUIView(uiview)
if g, ok := theUI.graphicsDriver.(interface{ SetUIView(uintptr) }); ok {
g.SetUIView(uiview)
}
} }
func IsGL() bool { func IsGL() (bool, error) {
return theUI.graphicsDriver.IsGL() 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
} }

View File

@ -52,8 +52,9 @@ var (
func init() { func init() {
theUI.userInterfaceImpl = userInterfaceImpl{ theUI.userInterfaceImpl = userInterfaceImpl{
foreground: 1, foreground: 1,
errCh: make(chan error), graphicsDriverInitCh: make(chan struct{}),
errCh: make(chan error),
// Give a default outside size so that the game can start without initializing them. // Give a default outside size so that the game can start without initializing them.
outsideWidth: 640, outsideWidth: 640,
@ -90,7 +91,8 @@ func (u *userInterfaceImpl) Update() error {
} }
type userInterfaceImpl struct { type userInterfaceImpl struct {
graphicsDriver graphicsdriver.Graphics graphicsDriver graphicsdriver.Graphics
graphicsDriverInitCh chan struct{}
outsideWidth float64 outsideWidth float64
outsideHeight float64 outsideHeight float64
@ -287,6 +289,7 @@ func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) {
return err return err
} }
u.graphicsDriver = g u.graphicsDriver = g
close(u.graphicsDriverInitCh)
// If gomobile-build is used, wait for the outside size fixed. // If gomobile-build is used, wait for the outside size fixed.
if u.setGBuildSizeCh != nil { if u.setGBuildSizeCh != nil {

View File

@ -18,10 +18,10 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
func SetUIView(uiview int64) { func SetUIView(uiview int64) error {
ui.SetUIView(uintptr(uiview)) return ui.SetUIView(uintptr(uiview))
} }
func IsGL() bool { func IsGL() (bool, error) {
return ui.IsGL() return ui.IsGL()
} }