Tip:
Highlight text to annotate it
X
BRUNO OLIVEIRA: Hello.
My name is Bruno Oliveira, and welcome to our third episode
in the Play Games and the NDK series.
We're going to talk about some threading and lifecycle issues
that may be tricky.
So first of all, let's start with threading.
So when you are using Play Games and NDK, what
you have to realize, all the time,
is that the UI thread is different from your game
thread.
So whenever you're writing your game logic,
that's going to be running on the game
thread on the native side.
And when ever you do most of the calls with he Play Games API,
you're going to want to do them from the UI thread
on the Java side.
So usually, one of the patterns that you
can use to manage the difficulty is just
create methods called post something something and then
those methods post to the UI thread.
And then on the UI thread, you actually
use the game helper to make the corresponding API call.
So something like this, post submit
score, which we covered on the last video,
you just call that from the native side
and then that posts to the UI thread.
How do you call this from the native side?
Well, you can just use J and I. But now,
how do you actually do the opposite?
How do you call from dalvik to native?
When you call from dalvik to native, all you have to do
is declare native method in your class,
and then implement the corresponding native method.
So for instance, here we are reporting that the sign in
has succeeded into the native code.
When you write the native method,
one thing that you have to look out for
is that you're actually running on the UI thread.
Even though you've crossed the native barrier
into native code, that doesn't mean
that you're running on the game thread.
You're actually running on the UI thread,
and if you start trying to manipulate your game objects
and game logic there, you might run
into very hard to debug issues.
So one of things you can do is just synchronize at that point.
However, synchronization may be expensive.
Like if you have a mutex or a semaphore
and so on, you're going to log that on every frame
that your game is going to draw, you
might end up with a very slow game.
So one of the very useful patterns we usually see,
is you want to do an atomic write,
especially when it's a very small data structure that you
just want to like pass a Boolean between threads,
you can do just an anatomic write.
So in this case, we are using the system dot atomic dot h.
So these are a few convenience functions
that allow you to do atomic writes and reads.
In this case, we are getting the new state from the UI thread
and then writing it atomically to that static variable called
state.
Because this means that when you try to read it from your game
thread, you can just read it because this
is going to be called for any threat.
You can just read it, because since writing it is atomic,
you don't have to worry about various conditions there.
So you get the state there based on that.
So this means you can actually get the state on every frame,
so you don't have to have a callback saying yes, anything
has succeeded or failed on the native side.
Because, in any event, you're going
to have to process every frame to do animations and such.
So you're already occupying the CPU on every frame,
so it doesn't really cost much more just to read a Boolean
variable to see if the user is signed in or not.
So it can eliminate some complications there that way.
So it can call a gets an in state on every frame
however, without having to write callbacks or anything.
So let's talk about the lifecycle of a Play Games app.
So back in the good old days, I remember that writing a game
was pretty easy because there was no lifecycle.
Essentially the game got started, the game got played,
and then the user stopped the game,
and it goes back to the operating system.
So that was a pretty easy time.
Now, with modern operating systems,
especially on mobile devices, you
have a whole bunch of callbacks that you
have to pay attention to.
So when you're game starts, you get onCreate
and then you get onStart and then you get onResume
and then you get the WindowFocus,
and then you get onPause.
And then you lose the window focus
because of the lock screen, and then you
get onStop because the user has gone back to the home screen.
And then you get onStart again, so it's
a whole bunch of callbacks.
Some of them are hierarchical, some of them are not.
Some of them come out of phase, and you have to pay addition
to all of those callbacks to know when you can and when you
cannot render stuff into your game.
Because sometimes you may even have a GL surface,
sometimes you don't have a GL surface.
Sometimes you should not be running.
Sometimes you should deallocate stuff.
So you have to pay attention to all those callbacks.
So it's important to keep track in your native game of where
in the lifecycle you are regarding those callbacks.
You have to know whether or not you
are between onResume and onPause.
You have to know whether or not you have the window focus.
You have to know whether or not you have a surface
or you don't have a surface, and so on and so forth.
And now, we're going to talk about how to integrate Play
Games into that flow because it's very important as well,
if you're using Play Games to know when you can make Play
Games API calls and when you cannot.
So let's try to simplify this lifecycle a little bit.
It's not as scary as it looks.
So normally, this is a basic Android activity lifecycle.
You get onCreate and you get onStart
and then you get onResume and onPause.
These events are all nested within each other,
so your only going to get onResume only after onStart.
And then before you get onStop, you're going to get onPause.
So you can have a variable that tracks
where in that state machine you are.
And then, of course, you're game should only be actively playing
between onResume and onStart.
And then, of course, there is an out of phase state machine,
which is the window focus, so we know
focus can come in and come out even if it's out of phase
with the other events.
So you should not assume that's going
to happened between onResume and onPause,
but you should assume that that may happen out
of phase with that.
So you have to keep track of when you have the window focus.
Other than that, if you're using OpenGL, and you most likely
are if you're using NDK, you also
have to keep track of when the surface gets created,
gets resized and gets destroyed.
So that's one more piece-- one more moving piece
that you have to keep track of.
So it's only when you have those five things that you
can actually do active game play.
So you have a surface, you have focus,
and the activity is in its resumed state.
So how does the Play Games API merge into this?
So this is where we have the game play, right?
So how do you deal with the Play Games API
lifecycle on top of that?
So fortunately, the Play Games lifecycle
is actually nested in the activities lifecycle,
so it's not actually as difficult as you might imagine.
So you have onStart and onStop, so
between onStart and onStop that's
when the Play Games activity lifecycle actually happens.
Typically, after onStart-- so there's
no way to keep a connection to Play Games
while your application is not started.
So whenever you get onStop, you actually
lose the connection to Play Games and have
to restore it on onStart.
If you're using Game Helper or basic Game Activity,
that restoration is done for you automatically.
So you don't have to worry about that,
but you have to know that it exists.
So whenever you get the onStart callback,
you're not connected to Play Games.
And then you have to wait for the onSignInSucceeded or
onSignInFailed callback.
So there is this window of time between onStart
and onSignInSucceeded where your activity is started,
it may even be resumed or have window focus,
you may even have the GL service,
but you might not be connected to Play Games because you're
waiting for the onSignInSucceeded or
onSignInFailed callbacks to be called.
Only after that point are you actually connected
to Play Games, and you can make API calls.
And remember that when you get onStop,
you get disconnected from Play Games.
So this is what the lifecycle looks like.
So to simplify this a little bit.
So what happens-- what may go wrong in this case?
So suppose that your game is actually running,
and then before you get the onSignInSucceeded callback,
you try to unlock something, you achieve something
very extraordinary and you want to unlock it,
and then you might run into trouble.
Because at that point in the lifecycle,
the Play Games API is not accepting API calls.
So if you haven't thought about that,
you're going to get a crash.
And if you have thought about that
and you're perfecting it with an is connected test, what's
going to happen is that achievement
is going to be lost.
So nobody wants that.
So one of the strategies that some games use
is that when you are between onStart and onSignInSucceeded,
you show a please wait screen.
This is usually not so bad because the interval
of time between onStart and onSignInSucceeded
is usually small.
If the user has already signed in before,
it's going to be pretty small.
If the user has never signed in before,
it's also going to be pretty small
because it's going to fail quickly.
So it's going to be very-- it's not
going to be typical for that to take more than say one or two
seconds.
So it's probably a good idea to put a please wait
screen between onStart and onSignInSucceeded.
Or of course, just handle that within the logic of your game.
So for instance, if every level starts
with this intro screen that's going to last five seconds,
then you don't need to put up a wait screen
because you know that SignInSucceeded is going
to happen before that pop-up-- I mean
before that story board scene goes away.
But it's nice to check for that anyway.
Once that please wait screen goes way,
you know you can safely do API calls.
So that's when you try to unlock something.
So this is one of the very useful patterns in that sense.
So the actual implementation is, of course, up to your game.
You can do something very smart, or you can do something much
more simplistic, something just like putting a please wait
screen, but the important thing is to notice that you have this
lifecycle issue, and you should be attentive in order not
to make any API calls when you shouldn't.
And I guess that's pretty much all there is to the lifecycle.
Thank you very much for watching,
and we hope you make great games.