Blazingly Fast JavaScript

Refactoring Set to Array



Blazingly Fast JavaScript

Check out a free preview of the full Blazingly Fast JavaScript course

The "Refactoring Set to Array" Lesson is part of the full, Blazingly Fast JavaScript course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen updates the code to use an array instead of a set for storing bullets in a game. They explain the changes they made to the code, including removing the bullet set from the player state, creating a public bullets array, updating the update function to iterate over the bullets array, and modifying the createBullet function to push bullets into the array. They also run tests to ensure that the changes did not introduce any errors and measure the performance improvement of the updated code.


Transcript from the "Refactoring Set to Array" Lesson

>> So I'm gonna do the world's simplest. So I'm gonna go back to the previous thing. So I'm gonna do the world's simplest form of programming, which is I'm gonna just update this to use an array instead of a set. So if you'd like to follow along, please do, there's not too much code that has to be ran to get this to work.

So I'm gonna first go up to the tippy top of the game/game.ts file, and I'm gonna just delete on each player state instead of having bullets as a set, I'm just gonna delete it right off. We're gonna delete it from the player state. I'm gonna delete it from create state, and so there we go.

So, the players no longer have their own bullet sets. I'm gonna be really lazy about this and just kind of test the theory. Does this actually work out? So I'm gonna go all the way down to the game, all the way down here. I know some of you see the word class, and you vomit a little bit, okay?

Calm down, okay? Sometimes state containers are a good thing, all right? Rust kind of has classes. I'm just saying a lot languages still have at least a notion of objects with functions on them. So calm down, all right? So I'm gonna go like this, public, let's call it public bullets, right?

I'm gonna make it public because that way it's easy to update our tests, pretty straightforward. So we have this nice little public function bullets in which I'm gonna go down to the constructor this.bullets = a new array, fantastic. I'm just gonna put all the bullets into a single array, and I'll check against that.

Just see, is there even a there or there, we're not even being as efficient as we could be. I just want the bookkeeping to be simple. I just wanna prove something out. All right, so let's go down all the way down to update. So we're gonna have some problems here, we can no longer use this.

So I'm gonna go for let I know again, people hate this idea of for loops, but they're actually quite nice, okay? You can run your own for loops from time to time. They're very simple, and they can be quite performant, okay? So I'm gonna save a reference to the bullet.

There we go. I'm gonna grab this update function, put it in there. And then after that, I need to check, hey, does this bullet hit player 1? So I'm gonna delete that, put it in there, awesome. Now we can go down, I can just delete out that previous for loop, do it again.

I'm gonna check, hey, do you hit player 2? We're not gonna be smart with our tests. Again, we're just seeing. So do you hit player 2? Yes or no? Okay, awesome. Delete that. And then lastly, I have to do this final for loop right here, which is, I need to test these bullets against the rest, right?

I need to see, hey, do you intersect any other bullets? So therefore, we don't we can remove those. So I'll just give it one of these and go let j equals, hey, do you already know what I'm gonna do copilot? It's like copilots read my brain. We need to go to effectively the next bullet in our list.

So think of something like insertion sort. We go to the next item in the list, and we're only gonna run what bullets are left, because we've already tested the previous ones. There we go. And so now we're gonna check, hey, do you collide? So I'll go like this, const b2= this.bullets [j], awesome, so we have b2 versus b1.

We'll log that out, but we can no longer use this. We don't need to have these whole bullets remove array. We're gonna be a little bit more clever about this. I'm gonna go this.bullets.splice. If you don't know the difference between splice and slice, it's really not that hard.

Splice takes things out, slice creates a view into, it's even in the name. You know, you can kind of guess what's happening here. So splice at j remove one element, at i--, I'm being really trickier, remove one element, so why am I doing that? Obviously, j is one ahead of i.

So we can just remove it safely, because our collection that we're iterating over is on i, but i is currently pointed to an element that we're currently on. So if I remove that, I gets moved forward, and then on our next loop, we would accidentally skip a bullet.

So I'm gonna just set i back one so when we increment our loop, we're onto the next new item. Pretty straightforward, pretty easy. I know people don't love for loops, but hey, we're for looping, okay? They don't call me off by one prime for no reason here. So I feel pretty confident that we've just done this all first try pretty well, very simple algorithm.

It's still bullet squared for its running time, but maybe it is better. Again, just checking for there or there. Lastly, we have this whole create bullet function, which attaches the bullet to the state. So we don't need to do that. I'm gonna go this.bullets.push. Boom, that's it, that's all we had to do.

Make sure it's ready. Now, the good news is I did create a couple unit tests and one integration test, which by the way, the integration test is just absolutely dog water. It may not work on your Mac. I just spawned a bunch of processes, definitely used some things that might be Linux online, I'm not even sure.

But it mostly works, and sometimes it leaves a zombie process running, so you have to make sure you kill it. It's just is what it is. I just wanted to prove to myself it somewhat works. So you can go like this npm run test. And this should run four game tests, so long as up look at that, look at that.

We forgot to do something, so let's go inside the test. So I'm gonna go inside the test, go to game, go to the next diagnostic. Look at this, see before I was relying on the fact that we're using a test, we're not using a test, or we're not using a set anymore.

So I'm just gonna, I'm gonna go CI this and go game.bullets.length. Remember when we made that public? I wanna make sure it's zero. There we go. We've updated our interface, absolutely wonderful. We're gonna rerun our tests, and what do we see here? Well, we should see four passing local tests, that's probably really good.

The integration test does take 30 seconds to run, because it literally spawns all the processes and plays one actual game, which takes about 30 seconds to get everything done. So absolutely fantastic. I'll let this one run out. We technically don't need this one to run out because we only changed how the game internal is played.

This really shouldn't have any effect on the overall thing, but for the sake of completeness, we'll do it. Awesome. Then I'm gonna do this little LS of 420, 420 or 42,000 kill 9. Yeah, it did look like we had our server still zombie there for a moment, but don't worry, it's dead now.

So we know that the game is working, everything is working fine. We made a really simple update to no longer use bullets, but instead use, or no longer use a set, instead use an array. And so, I'm gonna do the exact same thing we did before. I'm gonna do a little typescript compile, we'll use inspect, and we'll launch it on the log path of testing.

And I'm gonna go over here and run it with 1000 again. And we should go back here, and we should try to see, did we actually make this thing faster, cuz before update was taking effectively 14.4% of the time self time and 17.2 total time. Again, total time doesn't matter nearly as much as self time.

So I'm gonna let this thing kind of get fired up. It's running. We're feeling fast. Maybe we're even feeling a little faster, I don't even know. So we're gonna record. Let's just see, did we make an actual dent here? So I'm gonna record this for 10 seconds. By the way, do we have any predictions?

Of how much this will improve performance. Look at that, update is gone. It's still 3.8% of the time. What the heck happened? We didn't even do a great algorithm. It's like way faster. So sometimes, don't just reach for sets. You don't always need to reach for sets. Anyways, so update, clearly a W right now.

Does anyone have any predictions for how much better this is gonna make our game server overall?
>> 100%.
>> 100% faster, that's a bold move, Conor. No one else, okay? No one else decides they really want to make a guess. I'm gonna shut everything off, and we're gonna create a new login path and a new log here, so I'm gonna go remove, inspect.

Don't forget to do this. I'm just gonna call this first optimization 500. I'm gonna measure 500, 1000, 2000. We're gonna do the same stepladder and see does it affect anywhere within that because by 2000, you saw our server was just like almost like exclusively late. And so hopefully, we see some better performance.

So I'll start off, of course, with 500. We start this up, things are looking good. And I will cargo run, make sure I get 500 for my Q value, and now it's running. And so I'm gonna go over here, and I'm gonna open up a new tab, and I'm gonna go to local host for 2068, and go to temp/first optimization 500, okay?

So there we go. It's running. We'll let it run for a little bit. Again, this is most of performance stuff is doing this. So I'm gonna keep on going through the 500, the 1000, and the 2000. And then we'll see did we actually improve it.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now