all right so yes I'm here to talk to you
about tamagotchis and generators
excited that crystal went before me cuz
she already explained some concepts that
I'm gonna go into in this talk as well
so again hello I'm Jen software engineer
based in New York if you want to reach
out to me on twitter my handle is girl
code you know definitely tweet at me I
post like a lot of cat pictures and
stuff but there's also some JavaScript
usage all right how many of you had a
tamagotchi growing up oh yeah I see a
lot of hands sweet okay so if you aren't
familiar with them Tamagotchis were
really popular digital pets in the 1990s
so they looked like this and when the
game would start an egg would appear and
then hatch and out would come your
Tamagotchi as a baby and you want to
raise it from a baby to an adult so you
would clean up after it you would play
with it you would feed it it would be
Pat you would it needed things it could
get sick as well and then you didn't
actually know what type of Tamagotchi it
was gonna grow up to be as an adult so
that was the fun like surprise of the
game now when I was growing up i
nurtured a lot of Tamagotchis to
adulthood some didn't make it that's
okay but I really loved this game and I
just want to build my own like web-based
version of it so my version of a
Tamagotchi includes an SVG shell the
controls which are just HTML divs to
actually interact with the Tamagotchi
and then the game screen is actually
built in canvas so if you aren't
familiar with canvas to use it we're
going to query the Dom for a canvas
we're going to ask for the context so in
this case we want to D to draw on the
canvas we're going to use context draw
image and we're going to pass it the
image to draw as well as some XY
coordinates that I'm not gonna get into
right now but that's gonna draw the
image to the screen so at the bottom you
can see our Tamagotchi has been drawn to
the screen now for all the animations in
this game I used a sprite sheet so below
is the sprite to bounce the Tamagotchi
so if each frame of the sprite is at 200
pixels
then I need to draw the image at zero
then 200 and then 400 pixels so that's
going to bounce the Tamagotchi up and
then I'd go back down to bounce Tamaki
down now you'll notice that I'm doing
contexts that clear between these
drawings and that's to avoid this
situation so if you don't clear the
context between drawings all the frames
just draw on top of each other all right
so as it's written now this isn't going
to work it's actually drawing and
clearing so quickly that you're only
seeing the last frame of this so that's
not going to work but I can delay the
animation with that timeout so I'm
looking for this really old-school
choppy Pixley animation like the
original game and I'm not going to build
out a sprite sheet that is actually 60
frames right so I'm gonna animate from
frame 1 through 3 and then back to 2 and
1 for a full bounce using set timeout to
do this to delay the frame drawings so
this is going to work now ah bouncing
tamagotchi but I've created a problem
here
because now my animations are async so
how do I know when this animation is
done like I have to know the actual
amount of time that this is going to
take and that's in order to kick off
another animation after this or to not
you know overwrite another animation or
have a weird long delay between them and
you know this just isn't sustainable for
a game that's full of animations what I
want is to resolve an animation and then
handle another animation and this sounds
a lot like a promise now crystal REI
talked about promises but you know they
represent an adventure all value so
inside a promise I can run the animation
code and then when it's done I can
resolve the promise and since I can do a
dot then on the promise I can use that
to run an animation after it's complete
so for the first version of this game
that's what I did I used promises for
all
animations I made a general-purpose
animate function that takes in a draw
function and the milliseconds for the
delay animate always returns a new
promise and inside that promise an inner
function is going to run the draw
function and we delegate resolving the
promise to the draw function if drawl
resolves the promise and returns true
we're done otherwise set timeout is
called with the inner function and
another loop of this function occurs so
here we're going to actually use the
animate function to create a bounce up
animation we're gonna pass a function
that draws the correct frame clears the
context and increments the current frame
count when the current frame is greater
than the frame count the promise is
resolved and we return true so this
would stop the function from setting
another loop via set timeout and so now
we can create a simple bounce function
using our bounce up animation and it
bounced down animation using the same
animate function and as you can see our
Tamagotchi is bouncing wonderful
beautiful and so now that that's working
we can move on to creating the main game
loop so when you're not interacting with
the Tamagotchi it's going to show this
idle animation so it's going to bounce
it's going to move to the right move to
the left keeps bouncing it's so on and
so forth and to set up the main loop
we're going to create a loop function
will call the idle animation and when
that's complete we call the loop
function again so just continuously
loops there okay but I've created some
more problems here there's two main
issues I noticed when using promises to
build the animations
the first one is venable health so once
upon time we used callbacks to handle
async calls and you know this created
the dreaded triangle of doom okay
callback health and one of the issues I
noticed when making this game was that
promises really didn't alleviate
callback hell like you're still in hell
it's just
looking how so the left side is a more
realistic version of how these
animations might work so I might need to
pass in information to functions or
check information to make a decision and
this all gets just as unwieldy and as
unmanageable as callback hell and you
know don't mistake me for hating
promises they're really great but when
you're doing like more large-scale async
work the ease of promises really breaks
down and I noticed another larger issue
with using promises for animations so
the point of the Tamagotchi is that the
user can interact with it I mean the
whole game is about interacting with the
Tamagotchi so at some point I need to
suspend the idle animation in favor of a
user-generated event so like feeding the
Tamagotchi
so here I've added an array to hold
pending events the loop function checks
if there are any pending events and if
there are the handle event function is
run and the loop is terminated so handle
event is going to take the first event
off the queue of events and run it and
when it's complete we're gonna restart
the loop if there's another event in the
queue handle event will get called again
otherwise we continue the loop until the
next event and so this didn't work you
can see I'm requesting to feed the
Tamagotchi
but the feed animation is delayed until
the idle animation completes its current
loop and that can be upwards of seven to
eight seconds so that's not going to
work what I need is to actually cancel
the promise that runs the idle animation
except you can't cancel a promise think
of promises as unbreakable valves
rest in peace snake so you can throw
errors and promises and you can catch
those errors but you can't actually
cancel a promise all of the attached
debt functions are going to fire what I
actually want is to pause an animation
and yield to an event and that sounds a
lot like a generator so generators are
amazing because their code that can be
paused so most of the code that you
write is run to completion once it
starts there's no actually stopping it
even if you're doing async work you're
throwing it onto the event loop and it's
going to come back around you can't tell
it like to cancel itself and stop can't
pause it both generators you can pause
and resume code so let's take a look at
them so this is a generator and I know
it's a generator because of the asterisk
next to the function keyword that's
actually all you need to do to turn a
regular function into a generator
function and then inside you'll notice
the yield statement so yield is a
special keyword and generators that
means pause to use the generator we're
going to call it and that's going to
return to us the generator object now on
the generator object is a method called
next and that actually tells the
generator to run until it encounters a
yield statement when that happens it's
going to return to you an object with a
value key and a done key so if there's a
value to the right of the yield
statement
it'll be passed via the value key and
then done provides the status of the
generator is it done true or false so in
this case this generator is not done so
we're getting false now you don't need
to yield values yield is just a key word
that means pause so you don't need to
return a value with it so in that case
yielding no value is going to return
undefined and as you can see were still
not done done false so now we're calling
next again reading 3 so the value is 3
and done is false but done is still
false right like we haven't actually
finished yet it's on the next call
but now we're actually truly done you
can also pass values into the generators
next function and so here we're passing
two into the second next function and
this seems a little odd at first like
why aren't we doing this on the first
call to next but when you call next what
you're saying is hey generator do
whatever you need to do until you
encounter a yield statement so in this
case there's actually nothing for the
generator to do before the first yield
statement so when next is called it sees
that yield statement it's going to pause
there and the yield statement has no
value associated with it so the value is
undefined on the second next call we
pass in a value and that value is going
to take the place of yield and now
because we've called next and we've said
hey do whatever you need to do until you
see a yield statement the value is now
being assigned to the variable num the
generator runs until the next statement
and this time we do have a value yielded
back to plus Nam or four on the next
call the generator yields four plus num
or six then it's done no more yield
statements you can also create an
infinite generator so how many of you
use while loops yeah just just a handful
of you like in production it very few
okay generally we don't I mean when I
was learning while loops obviously what
I did was crash my browser I think we
all did so in this example like first
we're gonna pass in an initial value
into the generator function and this is
going to avoid relying on next to set
the initial value of num and then you're
gonna see the while loop right we're
doing wild true so this should crash my
computer like this is an infinite loop
and should crash my browser and I'll be
annoyed with myself but actually because
generators can pause it's actually
pausing
while loop so it's not gonna cause a
crash the yield statements going to
pause it for us and it's only gonna
resume again when we call next and so as
long as you keep calling next this is
going to keep running
so this couldn't be an infinite
generator but you don't have to worry
about overloading the event loop with
the while loop because generators can
pause now you can also run a generator
from inside of another generator and to
do that you're going to use the yield
asterisk expression I'm gonna call it
yield star because it's just a little
bit easier to say but to illustrate this
I have a generator function called
powder and a generator function called
inner and you'll notice that outer is
calling inner with the yield star
expression so same as before we're going
to call outer and that's gonna return to
us the generator object so the first
time we call next what we expect to
happen happens the January runs until it
encounters a yield statement so this
runs encounters yield and the value to
the right one is passed back as the
value but when we call next again the
generator isn't going to pause at the
yield star you can see it's pausing at
the first yield statement in the inner
generator and the value being returned
isn't the generator itself but the value
a from the inner generator so yield star
is a delegate so when you call next and
it encounters a yield star expression it
delegates the generator to the right of
that expression and it will continue to
do so as you call next until that
generator is complete so when we call
next again the inner generator isn't
done yet so now the value is B now the
inner generator is complete and so we
move on to the next yield statement in
the outer function and in addition to
being paused generators can be cancelled
and you can do this one of two ways so
the first is with the return statement
so this generator because it's using a
while true it should run for as long as
I call
the next method but instead when this
generator encounters the return
statement it's going to cancel itself so
you can see that done is now true and it
doesn't matter how many times I call
next this generator is done and you can
also cancel a generator from outside of
itself so here again the generator
should be infinite but when I call the
return method on it it cancels the
generator and this is really interesting
that you can cancel a generator from
within or outside because you can only
resume the generator from outside of it
so generators are paused via yield and
restarted via next but a generator
cannot call next on itself if I never
call next on this generator it will
never yield to it stays paused and this
is the thing I really struggled with
when I was converting the game from
promises to generators because who runs
the generator so if control happens
outside like something has to be pulling
the strings like how do you know to keep
calling dot next on something or how
many yield statements there are in
something and so this is where
co-routines come into play so carbo
teens are a general control structure
where control flow is cooperatively
passed between two different routines I
like to think of kuru teens as
cooperative partners because the
generator can't resume itself you need a
cooperative partner to help out so this
function is a basic example of a Co
routine it's going to take in a
generator it's going to call it which
returns the generator object the inner
function next response calls the
generators next method this starts the
generator which will run until it
encounters a yield statement at this
time the generator returns an object
with the value key and the done key next
response evaluates whether the generator
is done or not
calls itself and if not it calls itself
with the value from the generators
response and this loop continues until
the generator is done so the co routine
is the cooperative partner to the
generator the co routine function passes
control to the generator and when the
generator pauses control is yielded back
to the co routine and these two
functions cooperatively pass control
until completion so now I can rewrite
the bounce animation as a generator and
use a Co routine to run it except you'll
notice that this code suffers from the
same issue I had in the beginning the
animation still happens too quickly I
still actually need set timeout and I
actually still need promises but my
generator doesn't care if I'm using
promises it's not going to wait for each
individual promise to resolve and then
resume itself like it can't resume
itself but a cooperative partner could
so I can amend my Co routine to handle
async requests instead of calling next
response we'll call a handle async
function we'll pass it the value of the
generators response which is a promise
and now we can prevent the generator
from resuming until the promise is
resolved when it's resolved we'll call
next response which will resume the
generator
so with this amended Co routine these
functions are now equivalent so
previously we chained promises together
for the animation and now we can yield
promises and our cooperative partner the
KO routine will handle resuming the
generator when the promise is resolved
so you can do more than yield promises
you can yield all sorts of async things
like callback functions and generators
themselves and more and there's a really
great library called Co if you want to
play around with that I really highly
recommend it kuru teens are very very
powerful and the reason they're so
powerful is that they allow you to think
sequentially about async code because
the
her routine is your cooperative partner
and handling the messy business of
chaining and dealing with async code for
you in the background you can write your
code as if it ran sequentially so for
the second version of this game I
rewrote all the animations with promises
and generators so what you're seeing
here is actually the same idle animation
but with generators and promises and run
by a KO routine and I had two goals with
this rewrite I wanted to be able to
pause animations and I want to take
advantage of thinking sequentially about
async code so to do that I started with
rewriting the function that delays the
animation
so this is our async function and it
doesn't need to hold the animation
itself it just needs to delay with a
promise that's its sole responsibility
so when this function is called it
returns a new promise and resolved that
when the timeout is complete it also
broke out the context clearing it to its
own function and the drawing of the
image into its own function and now we
can combine these new functions to
replace the previous animation function
and we can do so inside a generator so
we'll clear whatever was previously on
the canvas and then draw the new frame
and those can occur synchronously will
yield the delay that's the asynchronous
part of this function but because we're
handing it off to the co-routine our
cooperative partner this looks and feels
very synchronous the KO routine will
handle the resolve promise when the
timeout completes and so here is our
draw frame generator in use this dislike
animation draws two frames and because
draw frame is a generator we're using
yield star to delegate yield calls to it
so for each call and draw frame we're
clearing the context drawing the frame
and then delaying and then this is what
the dislike animation looks like you can
see it's a very unhappy tamagotchi just
really pissed okay and now we can
rewrite also the main game loop and take
advantage of canceling the generator so
our loop generator function will be run
by ko
so we can rely on it to handle any
values yielded to it we'll create a
variable to save the status of the
generator done or not and we'll preset
the animation to idle
we'll use a while loop to create a
never-ending generator so this way the
game loop is going to continue forever
if we have a pending user event we'll
cancel the current animation and
delegate to the event using yield star
and whatever the event yields will be
passed to the co routine otherwise a
second while loop will handle running
the animation a while loop inside a
while loop I want you to understand
though this is nuts
as long as the animation is not complete
and there are no pending events this
loop will yield the generators values to
the KO routine to handle and these are
promises so the KO routine is going to
handle the chaining but if the animation
is done or there is a pending user event
the loop is going to terminate stopping
the animation generator from running
again and will reassign animation to the
idle generator to start from fresh and
it will reset done to false and on the
next iteration of this while loop
pending will be true and will handle the
user event instead so this is the
generator function for the feed action
it evaluates if the Tamagotchi is hungry
or not if it's not it delegates future
calls to the dislike anim generator
which will run the dislike animation
otherwise we're gonna decrease the
hunger count and use the yield star
we're gonna delegate to the eat
generator that runs that animation all
right so will this work all right so
we're idling I'm gonna say yeah I want
to feed you I'm gonna feed you a burger
he's eating all right so the idle engine
canceled I'm gonna do it again but this
time I'm a feedom candy in a second
cuz you know a well-balanced meal there
we go
cool all right he seems fine so idle
animation keeps running again
all right let's feed him one more time
cuz I think maybe he's still hungry now
he's mad he doesn't like that alright
so that's LL for me if you want to check
out the code for this there's the link
to the repository for it and huh
[Applause]