Placeholder Image

Subtitles section Play video

  • all right.

  • So, yes, I'm here to talk to you about Tommy got cheese and generators, and I'm really excited that Crystal went before me because she already explained some concepts that I'm gonna go into in this talk as well.

  • Eso again.

  • Hello.

  • I'm gen software engineer based in New York.

  • If you want to reach out to me on Twitter, my handle is girl code, you know?

  • Definitely tweeted 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 time?

  • I got you growing up.

  • Oh, yeah, I see a lot of hands.

  • Sweet.

  • Okay, so if you aren't familiar with them, Tommy got cheese Were really popular digital pets in the 19 nineties.

  • 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 beep at you with it, needed things.

  • You could get sick as well, and then you didn't actually know what type of Tom Adachi 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 Thomas watches to adulthood.

  • Some didn't make it.

  • That's okay.

  • Um, but I really love this game, and I just want to build my own like, Web based version of it.

  • So my version of a Tom Adachi includes an SPD shell, the controls which are just ht motives to actually interact with Tom Adachi.

  • 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 clear the dom for canvas.

  • We're going to ask for the context.

  • So in this case, we want to d and to draw on the canvas, we're going to use context, outdraw image.

  • We're gonna pass it the image to draw as well as some X Y coordinates that I'm not going to get into right now.

  • But that's gonna draw the image to the screen 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 Tom Adachi s O.

  • If each frame of the sprite is at 200 pixels, then I need to draw the image of zero, then 200 and then 400 pixels.

  • So that's going to bounce.

  • The Tommy got you up and then I'd go back down to bounce time, but she down.

  • Now, you'll notice that I'm doing context 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 gonna work.

  • But I can delay the animation with set time out.

  • So I'm looking for this really old school choppy Pixley animation like the original game.

  • And I'm not gonna build out a spreadsheet that is actually 60 frames, right?

  • So I'm gonna animate from frame one through three and then back to two and one for full bounce using set time out to do this to delay the frame drawings.

  • So this is going to work now.

  • Ah, bouncing Tom A catchy.

  • But I've created a problem here because now my animations are a think.

  • 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 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 the sounds a lot like a promise.

  • Now Crystal Wright talked about promises, but you know, they represent an eventual value.

  • So inside a promise I could run the animation code.

  • And then when it's done, I can resolve the promise.

  • And since Aiken 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 the animations.

  • I made a general purpose and make function that takes in a draw function in the milliseconds for the delay.

  • Enemy always returns anew.

  • Promise.

  • And inside that promise, an inter 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 time out is called with the Inter 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 time out.

  • And so now we can create a simple bounce function, using our bounce up animation and bounce 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 Tom Adachi.

  • It's gonna show this Idol animation so it's gonna bounce, is gonna move to the right move to the left, keeps bouncing it so on and so forth.

  • And to set up the main loop, we're going to create a loop function.

  • We'll call the Idol animation, and when that's complete, we call the loop function again.

  • So just continuously lips there.

  • Okay, but I've created some more problems here.

  • There's two main issues I noticed when using promises to build the animations.

  • 1st 1 is Venable Health.

  • So once upon a time, we used callbacks to handle a sink calls.

  • And you know, this created the dreaded triangle of Do Take a 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 a different looking help.

  • So the left side is a more realistic version of how these animations might work.

  • So I might need to pass on information to functions or check information to make a decision.

  • And this all gets Justus unwieldy and as unmanageable as callback health.

  • And, you know, don't mistake me for hating promises.

  • They're really great.

  • But when you're doing like more large scale a zinc work, the ease of promises really breaks down.

  • And I noticed another larger issue with using promises for animations.

  • So the point of the Tom Adachi is that the user can interact with it.

  • I mean, the whole game is about interacting with the Tom Adachi.

  • So at some point, I need to suspend the Idol 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 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.

  • 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 time I got you.

  • Um, but the feet animation is delayed until the Idol animation completes its current loop and that could be upwards of 7 to 8 seconds.

  • So that's not going to work.

  • Um, what I need is to actually cancel the promise that runs the Idol animation.

  • Except you can't cancel a promise.

  • Think of promises as unbreakable valves rest in peace.

  • Snake.

  • Um, so you can throw errors and promises and you could catch those errors, but you can't actually cancel a promise.

  • All of the attached dot that functions are going to fire would actually want is to pause and animation and yield to own event.

  • And that sounds a lot like a generator.

  • So generators are amazing because their code that could be paused.

  • So most of the code that you write is Roger completion.

  • Once it starts, there's no, Actually, it's stopping it in.

  • If you're doing a sink 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.

  • Stop.

  • Can't pause it.

  • Bo generators.

  • You can pause and resume coat, so let's take a look at them.

  • So this is a generator, and I know it's a generator because of the Asterix.

  • Next to the function key word 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 key word 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 and object with a value key and a donkey.

  • So if there's a value to the right of the yield statement, Italy 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 pots, 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, we're still not done done false.

  • So now we're calling next again, reeled in threes of the value is three and Dennis false.

  • But then it's still thoughts, 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.

  • 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?

  • Um, but when you call next, what you're saying is, hey, generator, do whatever you need to do until you encounter 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 seem it has no value associated with it, so the value is undefined.

  • On the second next call, we pass in the value and that value is going to take the place of guilt.

  • 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 numb.

  • The generator runs until the next statement.

  • And this time we do have a value yielded back to plus numb or four.

  • On the next call, the generator yields four plus numb or six.

  • Then it's done.

  • New Maria old statements.

  • You can also create an infinite generator.

  • So, uh, how many of you use wild loops?

  • Yeah, just just a handful of you, Like in production.

  • Very few.

  • Okay, generally we don't.

  • I mean, when I was already while loops, obviously what I did was crashed my browser.

  • Um uh, 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 numb.

  • And then you're gonna see the wild loop right?

  • We're doing while 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 the wild loop.

  • Um, so it's not gonna call the crash the yield.

  • Same.

  • It's going deposit for us.

  • And it's only going to resume again when we call next and so as long as you keep calling next, this is going to keep running.

  • So that's gonna be an infinite generator.

  • But you don't have to worry about overloading the event loop with wild because generated composite.

  • Now, you can also run a generator from inside of another generator, and to do that, you're gonna use the yield Asterix expression I'm gonna call it.

  • Yields start cause it's just a little bit easier to say.

  • But to illustrate this, I have a generator function called outer and a generator function called dinner, and you'll notice that outer is calling inner with the yield star expression.

  • So same as before, gonna 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 or 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 you old store is a delegate, so when you call next and 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 you call next again, the inner generator isn't done yet.

  • So now the value is B.

  • Now the inner generators complete, and so we move on to the next deal statement in the outer function and in addition to being paused, generates could be canceled and you could do this one of two ways.

  • So the first is with the return statement.

  • Eso this generator because it's using a wild.

  • 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.

  • You can see that done is now true.

  • 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.

  • You 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 something 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 and something.

  • And so this is where Cho routines come into play.

  • So your routines are a general control structure where control flow is Cooper flee passed between two different routines.

  • I like to think of Curry 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 Ako routine it's going to take in the generator.

  • It's going to call it which returns the generator object.

  • The inter 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 donkey.

  • Next response evaluates with a D generator, is done or not calls itself, and if not, it calls itself with the value from the generators response.

  • And this Luke continues until the generator is done.

  • So the Coe routine is the cooperative partner to the generator.

  • The Corps 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 past 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 time out, 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, promised to resolve um and then resume itself.

  • It can't resume itself but a cooperative partner could.

  • So I can amend my co routine to handle a sink requests instead of calling.

  • Next response will call a handle a Sikh 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 will call next response, which will resume the generator.

  • So with this amended code 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 co 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 a sink, things like callback functions and generators themselves and Maur Um, and there's a really great library called CO.

  • If you want to play around with that, I really highly recommend it.

  • CA routines are very, very powerful, and the reason they're so powerful is that they allow you to think sequentially about a sink code because the co routine is your cooperative partner in handling the messy business of chaining and dealing with a sink 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 idol animation, but with generators and promises and run by a co 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 a sink code.

  • So to do that, I started with rewriting the function that delays the animation.

  • So this is our A sink function, and it doesn't need to hold the animation itself.

  • It just needs to delay with a promise that's its sole responsibility.

  • Eso, When this function is called, it returns anew promise and resolve that when the time out is complete, also broke out the context, clearing into its own function and the drawing of the image and 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 ace.

  • Ingrid is part of this function, but because we're handing it off to the co routine, are cooperative partner.

  • This looks and feels very synchronous.

  • The co routine will handle the resolve promise when the timeout completes.

  • And so here's 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 could say 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 Cho routine so you can rely on it to handle any values yielded to it.

  • We'll create a variable to say the status of the generator, done or not, and will preset in animation tidal.

  • We'll use a wild 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 will cancel the current animation and delegate to the event using Yield Star and whatever the event, yields will be passed to the cover team.

  • Otherwise, a second wild loop will handle running the animation a wild loop inside a while.

  • Look, I want you understand 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 co routine to handle and these air promises, so the Corps routine is gonna handle the training.

  • But if the animation is done or there is a pending user event, Loop is going to terminate stopping the animation generator from running again.

  • And we'll reassign animation to the Idol generator to start from fresh and we'll reset done defaults and on the next iteration of this wild loop pending will be true, and we'll handle the user event instead.

  • So this is the generator function for the feet.

  • Action evaluates if the Tamagotchi is hungry or not.

  • If it's not the delegates.

  • Future calls to the Dislike Enemy generator, which will run the dislike animation.

  • Otherwise, we're gonna decrease the hunger count and use 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, when feed you feed your burger is even.

  • All right, So Vital nation canceled.

  • We'll do it again.

  • But this time I'm a feed him candy in a second because, you know a well balanced meal.

  • There we go.

  • Cool.

  • All right.

  • Hey.

  • Seems fine.

  • So I don't Animation keeps running again.

  • All right, let's feed him one more time.

  • I think maybe he's still hungry.

  • Now he's mad, isn't like that.

  • All right, so that's l for me.

  • If you want to check out the code for this, there's the link to the repository for it.

all right.

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it