[00:00.000 --> 00:12.680] Hi, everyone! Hi, Fosdame. How are you doing? I hope that you're doing great. Yeah! Let's [00:12.680 --> 00:19.560] go! So I'm really honored to present my talk about how I ported Doom to the browser with [00:19.560 --> 00:26.480] Blazor Wazen. So it's a topic about another language, which is, we will see. So a quick [00:26.480 --> 00:32.840] word about myself. So I'm Yessin Ben-Abbas. I'm a DevRel at Wordline. I'm a teacher also. [00:32.840 --> 00:40.320] And during my spare time, I love to play video games. So that's one of the things that made me [00:40.320 --> 00:48.240] make this port. But before going further, let me explain what is a port. So game porting consists [00:48.240 --> 00:54.360] of making a game run in another platform other than the original one. There are many ports that [00:54.360 --> 01:00.880] are released nowadays. Some are good, some are bad, depending on how they are developed. And it [01:00.880 --> 01:10.080] consists of adapting the source code of the original game into the new platform. So adapting means [01:10.080 --> 01:15.360] that maybe we need to change some bits of the code. Maybe sometimes it can be a whole rewrite, [01:15.360 --> 01:22.800] depending on the differences between the platforms and how the game has been developed. And using [01:22.800 --> 01:28.640] a virtual machine or an emulator is not really considered as porting. You really need to have [01:28.640 --> 01:36.840] access to the source code and adapt it to the new target platform. So in the beginning, I wasn't [01:36.840 --> 01:43.280] really confident with making a port. I considered it as a complex task, difficult. I didn't have a [01:43.280 --> 01:50.400] clear vision on what it is. And what really gave me a first inspiration to consider porting games [01:50.400 --> 01:59.960] is modern vintage gamer. Who knows about MVG? Yes. So he's a game developer, a YouTuber who makes [01:59.960 --> 02:07.040] great videos. I didn't expect it, but he made a video where he showed how to port Heart of Darkness, [02:07.040 --> 02:14.760] one of the greatest games of retro gaming. And he showed how he ported it to the original Xbox. [02:14.760 --> 02:21.880] So he made a video where he showed changing includes. So watching this video really made me [02:21.880 --> 02:29.080] more confident and considering porting in one of my activities. But that's not the only thing [02:29.080 --> 02:36.040] that gave me the idea of porting a game. The other thing is I like to play with.NET framework. [02:36.040 --> 02:44.840] I really like this framework. Because it has many good things. Some of them is that it's an [02:44.840 --> 02:52.240] open source, cross platform, general purpose framework. So it runs on Linux, Windows, Mac, [02:52.240 --> 03:01.120] Android, iOS, a lot of platforms. And even other platforms that I'll talk about now. And the language, [03:01.120 --> 03:05.880] the main language of this framework is C-Sharp. So C-Sharp is really, really good language that keeps [03:05.880 --> 03:12.120] evolving over the years. It has some features that you can find in modern languages, null safety, [03:12.120 --> 03:20.040] extension functions, and this kind of stuff. So it really keeps evolving great language. However, [03:20.040 --> 03:29.960] in the beginning of the framework, the browser was not target. But in 2020, with the release of.NET [03:29.960 --> 03:39.640] 5,.NET introduced the support, the Blazor wasm framework. So it's a component-based framework, [03:39.640 --> 03:48.000] like Angular, React, and view with components. But the code of the component is in C-Sharp. And it [03:48.000 --> 03:54.760] runs locally, natively on the browser, thanks to a WebAssembly stack, as you can see here. So Blazor [03:54.760 --> 04:05.240] has WebAssembly implementation, which allows the developer to access Razor components and also [04:05.240 --> 04:12.200] access the.NET framework, and also communicate with the DOM. So this is an example of a Razor [04:12.200 --> 04:19.120] component. It's similar to what you see in Angular or View. The difference is that the code below is [04:19.120 --> 04:28.800] in C-Sharp. In addition to that, you can have also CSS, of course, and you can even call JavaScript [04:28.800 --> 04:37.440] and it interoperates with C-Sharp. So when I saw this, C-Sharp.js, which interoperates, I was like [04:37.440 --> 04:45.680] this. I was amazed. I was really happy to see this. And I told myself, it's time to make this port. [04:45.680 --> 04:55.440] So now I need to find a game to port. There are many games with source code available. And the [04:55.440 --> 05:02.280] game that I chose, no spoiler here, it's Doom. So I will tell you next why I chose Doom. But one [05:02.280 --> 05:09.560] of the reasons is that it's one of the most successful first-person shooters. And not footers, [05:09.560 --> 05:17.040] shooters, sorry, for the stick. And also, technically speaking, it's well developed, in my [05:17.040 --> 05:23.920] opinion, because the logic of the game is separate from the resources. So you have the famous WAD [05:23.920 --> 05:31.960] files of Doom. So it contains the assets of the game. And you have really what updates the game [05:31.960 --> 05:38.800] state, the position of the character, or the game logic in a separate project. So that allows [05:38.800 --> 05:46.000] to have Doom being portable by design. And in terms of ports, Doom has a lot of them in video game [05:46.000 --> 05:51.360] consoles, of course, and even anything that has a screen and some processing, as you can see here. [05:51.360 --> 05:58.680] And there are even more. So comes the reason why I chose Doom, because I found that there is a [05:58.680 --> 06:05.920] .NET port already existing of Doom, of Linux Doom, which is the source code released by [06:05.920 --> 06:13.760] id Software. And in GitHub, there is a repository which has developed a port of Doom in.NET. [06:13.760 --> 06:20.920] However, this port uses libraries that communicate with hardware, like graphics, audio input, [06:20.920 --> 06:30.560] which are not compatible with the browser. So that's why my work was to take this port of Doom [06:30.560 --> 06:36.120] and make it work on the browser. So just to be clear, I used the V1 of Managed Doom, [06:36.120 --> 06:43.160] because currently they are developing a V2, which uses another library, but just to be [06:43.160 --> 06:48.280] clear on my work. So to summarize, id Software released the source code of Doom for Linux. [06:48.280 --> 06:57.560] Since you developed Managed Doom, which targets any platform that is targeted by SFML, desktops [06:57.560 --> 07:05.480] mostly. And this is what I intervene to base my work on this port and make it work on the browser. [07:05.480 --> 07:17.440] So before starting work, my porting, I made a strategy which is this one. So this is an AI image, [07:17.440 --> 07:24.600] by the way. I tape Doom monster typing on keyboard, and I got this. So my porting strategy was to [07:24.600 --> 07:33.240] get something that works like proof of concept that works quickly and to demonstrate quickly. So [07:33.240 --> 07:39.200] the first step is to take the source code and compile it with the Blazor framework, as simple as [07:39.200 --> 07:45.240] it is. And as soon as I see a compilation error, I delete the code and I add the to-do. So another [07:45.240 --> 07:54.800] presentation with to-dos, that's fine. So after that, once the code compiles, I replaced little by [07:54.800 --> 08:00.960] little bits of code that are not implemented, or the methods or functions that are not implemented, [08:00.960 --> 08:05.360] by giving priority to frame rendering, because it's always nice to see something on the screen, [08:05.360 --> 08:15.680] rather than working blindly. And in terms of optimization, I always left that to later, unless [08:15.680 --> 08:23.480] it's really necessary. And in terms of reading documentation, so it's really well documented, [08:23.480 --> 08:28.960] how Doom is implemented, but I only read the parts which are really relevant and important, [08:28.960 --> 08:40.240] specifically how the Doom image is drawn on the screen, when the frame data is generated by the [08:40.240 --> 08:48.920] engine. And with this kind of porting strategy, like two, three weeks of part-time or site project [08:48.920 --> 08:55.600] work, I was able to achieve something, a port that can be run, executed, even if it's not perfect [08:55.600 --> 09:05.920] yet, but we'll see in the demo later how it works. Now let's enter into more details on how I ported [09:05.920 --> 09:15.760] more concretely this into the browser. So first of all, before giving more further explanations, [09:15.760 --> 09:23.080] let me show you how game is developed most of the time. So it's a big picture of the game [09:23.080 --> 09:29.800] algorithm. First of all, we have a wide loop, which is an infinite loop, but it doesn't iterate as [09:29.800 --> 09:35.760] soon as possible. It iterates only when the frame-pacing is relevant. For example, if you have [09:35.760 --> 09:43.800] 30 FPS game, this next iteration will wait a little bit if the previous frame was computed [09:43.800 --> 09:52.160] very quickly. So it allows to have a frame-pacing which is correct and nice to the eye for the user. [09:52.160 --> 09:59.480] So once the frame is ready, we get the user input compared to his previous frame. Really simple. [09:59.480 --> 10:09.600] And after that, we run a frame or we compute the next frame of the game. So we run the update [10:09.600 --> 10:16.480] game state. It's just an example name of the method. It takes the input of the user, the [10:16.480 --> 10:23.200] what file for the doom in this case. And then it advances the game one frame. So it updates the [10:23.200 --> 10:28.560] player position, the monster position, the ammo, the status, his life, all this kind of stuff. [10:28.560 --> 10:36.640] And it also generates to be rendered a frame and some audio. And this is run. So for each frame, [10:36.640 --> 10:44.640] this algorithm is run and it updates the game each frame. And once we get a frame and some [10:44.640 --> 10:51.480] audio, we play them and render them to the user. So when you see this, you can start guessing [10:51.480 --> 10:57.840] which parts. So in the managed doom, this all is done in C-sharp. That's clear. And you can start [10:57.840 --> 11:03.280] to see which parts need to be adapted for the browser, which are not available in C-sharp, [11:03.280 --> 11:10.840] but need to go to the JavaScript realm to be able to achieve it. But to show you what I [11:10.840 --> 11:19.120] ported more precisely, let me show this in another way. So here. So we have the while loop and the [11:19.120 --> 11:26.720] frame-pacing step here. Next, the user input is sent to the update game state with the what [11:26.720 --> 11:34.640] file as argument. And then we generate some audio and the frame to be rendered. And it loops. [11:34.640 --> 11:42.320] So this is what needs to be ported, what you see in red. So what you see on the top is frame [11:42.320 --> 11:49.600] pacing. It's not really Blazor relevant, but browser relevant. For a frame pacing, [11:49.600 --> 11:56.880] there is a better way to base frame in JavaScript, base frames in JavaScript. And to render, [11:56.880 --> 12:04.200] since SFML is not available in Blazor, so this needs to be replaced. Also the update game states [12:04.200 --> 12:11.160] which is also, so everything is in C-sharp. Update game state, even though it's a platform [12:11.160 --> 12:18.520] agnostic, it's not 100% the case. So there needs to be some bits that needed to be adapted to the [12:18.520 --> 12:25.520] browser. But hopefully 70% of the code approximately was across platform and runs on the browser [12:25.520 --> 12:34.600] without any problem. So after some work, some coding, some fun, some fails and learning, [12:34.600 --> 12:42.040] I achieved this result. So I replaced the white loop with request animation frame. Anyone knows [12:42.040 --> 12:50.920] about request animation frame here? Yes. Nice. So yeah, request animation frame is how you tell [12:50.920 --> 12:56.440] the browser, so I want to render frames in an optimized manner for the browser. For example, [12:56.440 --> 13:01.400] when you switch a tab, don't do anything to optimize energy. So you ask the browser, request a [13:01.400 --> 13:08.040] new frame. When you see it relevant to compute a new frame for my game, call me back. So it's a [13:08.040 --> 13:15.960] callback. And for each frame, we call it back. After that, so once this has been changed, [13:15.960 --> 13:24.240] change also the rendering. So for the audio, it's the audio context library. And for rendering, [13:24.240 --> 13:34.200] it's the canvas, of course. So audio context is the audio API of the browser. But there is one [13:34.200 --> 13:41.320] thing that I didn't mention yet, and that you see here, is that in this state, since I was, [13:41.320 --> 13:46.440] as I said earlier, Blazor is a component-based framework. It's like Angular View React. You [13:46.440 --> 13:52.280] need to have some kind of main component, which is the entry point of your program or of your [13:52.280 --> 14:00.160] component. So here it's missing. So that's why I added or I had to have a Blazor component, [14:00.160 --> 14:06.680] which only serves as the entry point to invoke the JavaScript, which then goes back to C sharp. [14:06.680 --> 14:17.520] So this is C sharp.net, C sharp. So when I say C sharp.net, I say them interchangeably. So this [14:17.520 --> 14:22.360] is JavaScript. We go back to C sharp. We go back to JavaScript. So there is a lot of context [14:22.360 --> 14:30.400] switching or language switching. And this is achieved thanks to this API. So Blazor provides [14:30.400 --> 14:36.440] an API that allows to go back and forth from a language to the other. So this is Blazor way [14:36.440 --> 14:44.040] of doing things before.NET 7. Starting.NET 7, there is even a better way to do this. I'll show [14:44.040 --> 14:52.640] it at the end of the presentation. Okay. So now we have something that runs. So I will show you, [14:52.640 --> 14:58.360] quickly show you for the audio parts, some code, and then I will continue the last, [14:58.360 --> 15:05.560] or maybe just the entry point and then continue the presentation. So this is the main component. [15:05.560 --> 15:12.240] As you can see here, which in the code, so we have the canvas here. And here we have, [15:12.240 --> 15:18.520] we initialize the DOM object or the game object. And then here we invoke the JavaScript method [15:18.520 --> 15:26.720] that calls request animation frame. So we invoke the JavaScript method here. We here handle the [15:26.720 --> 15:35.600] frame pacing. Okay. Here we handle the frame pacing. And then we call back.NET to run an iteration [15:35.600 --> 15:41.520] of the game, the DOM engine to run a computer frame. And then we call request animation frame to [15:41.520 --> 15:47.400] prepare for the next frame. Which calls back this method. So this is like an infinite loop. [15:47.400 --> 15:55.240] And this method that you see here, which invokes.NET code, just invokes the game objects and [15:55.240 --> 16:02.040] requests it to render a new, to compute a new frame with the user input. So this, [16:02.040 --> 16:08.040] I will just skip it. So this is how audio and video are rendered. So it's communication between [16:08.040 --> 16:23.120] C sharp and JavaScript. And I continue. So, so what I learned from this in Blazor, avoid [16:23.120 --> 16:33.320] copying arrays, big arrays. In the beginning, in the.managed DOM source code, the final image is [16:33.320 --> 16:39.880] generated by copying, converting a one-dimensional array into a 2D array. So this slowed down the [16:39.880 --> 16:45.520] game a lot, a lot, a lot. So I removed this part from the managed DOM source code. And I sent it [16:45.520 --> 16:51.400] to JavaScript. That's what you, what I was, was shown in the previous slide about frame rendering. [16:51.400 --> 16:58.480] And I don't have to cover it. But yeah, avoid copying big arrays in.NET. This is in.NET 5. [16:58.480 --> 17:05.320] Maybe in.NET 7 it has been improved. Avoid extensive logging. And calling Blazor from [17:05.320 --> 17:11.640] JavaScript from Blazor communication can be very fast if you use the correct API. As I said, [17:11.640 --> 17:18.280] however, this, the API that I used is undocumented. And I confirm it because I found no documentation, [17:18.280 --> 17:24.280] just some source code or some obscure GitHub repositories. But hopefully in.NET 7 it's, [17:24.280 --> 17:30.400] it's improved. In JavaScript, I learned that request animation frame is the way to paste frames. [17:30.400 --> 17:36.960] And to play audio programatically, you need to have some user interaction before or the [17:36.960 --> 17:51.640] audio context API doesn't work. So here is the demo. So I click the, to enable the audio, [17:51.640 --> 18:04.840] the user interaction. And let's see. Yes, of course. Yes. And here we go. So just to show [18:04.840 --> 18:16.880] you that we have sound. So don't be afraid. It's just a game. And just to show you that you also [18:16.880 --> 18:21.640] have secret passages. I don't know if you know this one. You have 200 armor. But that's another [18:21.640 --> 18:36.320] topic. And it runs correct frames. It's a 2012 Macbook and it runs at 30 FPS. No problem. Okay. [18:36.320 --> 18:44.360] So last two slides, the interrupt in.NET 7. So here's how interrupt works now. You don't need [18:44.360 --> 18:48.640] Blazor. It means you don't need to create component if you want to interact between JavaScript and [18:48.640 --> 18:54.200] .NET anymore. And I'm working currently on this part because it's really exciting to see this [18:54.200 --> 19:00.360] kind of work. So to call JavaScript methods from.NET, you just need to export your JavaScript [19:00.360 --> 19:10.360] method as you do in any JavaScript module. And you call here, you just import the method and you [19:10.360 --> 19:16.680] can have access to it. And in the opposite sense, you just export your.NET method and then you [19:16.680 --> 19:24.760] import it in JavaScript using this kind of code. And that's it. So I'm working on changing how the [19:24.760 --> 19:32.400] game is ported to this. And in terms of next step, then, it's to migrate to JS interupt. Update to [19:32.400 --> 19:39.160] manage Doom V2. Maybe I will gain some more performance. After that, I would like to have some [19:39.160 --> 19:45.080] game music and also to be able to play other wads. Currently, only the Doom one works. I don't know [19:45.080 --> 19:53.160] yet why. And as long-term, really, it's also a wish. Maybe this can be integrated to the official [19:53.160 --> 20:04.440] managed Doom project. So as a conclusion, wasm makes existing code compatible with browser. It [20:04.440 --> 20:11.960] means that, I mean, wasm is not just the very fast JavaScript alternative. It also opens the way [20:11.960 --> 20:17.600] to make many, many languages, many, many technologies run on the browser. So that's really what I [20:17.600 --> 20:23.320] like. What's really exciting for me, at least about wasm, and porting games is fun. Developing is [20:23.320 --> 20:37.520] fun. Do you agree? Yes. Thank you very much. Thank you. So we have time for a couple of questions. [20:37.520 --> 20:51.240] Who wants to ask the first question? Thank you. Hi. First thanks a lot. It was really, really [20:51.240 --> 20:56.760] insightful. Thank you. I have a question about request animation frame. I think I saw, so, [20:56.760 --> 21:02.360] because request animation runs at 60 FPS, right? And then I saw you do something with [21:02.360 --> 21:10.760] timestamps to try to do 30. Yes. Does it ever drop or become inaccurate? Or is it just, [21:10.760 --> 21:21.280] is that like the right way to achieve 30 FPS? Yeah, I guess it's, maybe, I'm not a JavaScript [21:21.280 --> 21:30.320] specialist on this. Me neither. I'm curious. It's here, I guess. Yes, it's here. For me, [21:30.320 --> 21:36.200] it worked. Yes, I didn't have eyes here. That's what I found on the Internet. I tried it. And [21:36.200 --> 21:40.920] I've seen the demo. It doesn't drop. When it drops, it's really when there is a lot of things [21:40.920 --> 21:49.240] happening. When there is a lot of audio, it's still not optimized a lot, the audio part. But this [21:49.240 --> 21:56.400] frame-pacing, for me, it works. So you compute the duration between the last request frame and [21:56.400 --> 22:02.360] the new request frame. So for me, it's okay. Excellent. Thank you very much. You're welcome. [22:02.360 --> 22:14.320] Next question. Yeah. The next speaker who is speaking after? Nobody? Nobody is speaking [22:14.320 --> 22:20.560] after you? I mean, who is taking that seat who are standing there? Okay, we'll call them. [22:20.560 --> 22:26.560] So it's a follow-up to the previous question, actually. Have you tried removing this check [22:26.560 --> 22:34.760] and see how fast you could run the game? Can you do 1,000 FPS? No, it's not 1,000. No. I actually [22:34.760 --> 22:42.360] tried to remove, but don't remember, but it's not 1,000 FPS. For sure, it's certain. It's not [22:42.360 --> 22:51.160] really, really fast also. Like maybe to 40, 50 FPS, it depends also on the machine. It depends [22:51.160 --> 22:56.040] on the hardware that you have. On the processor hardware that I have, I don't have a gaming [22:56.040 --> 23:04.120] computer. It was like maybe 40, 50 FPS. Okay, thanks. But yeah, that's a good question because [23:04.120 --> 23:11.760] you see when we talk about good ports, bad ports, for example, this, I mean, it's a quick to achieve [23:11.760 --> 23:16.480] port, but it's not the most optimized one. So that's when you see game companies making ports. [23:16.480 --> 23:23.320] And also, for example, when I said that the array copy on.NET is slow. So at the same time, [23:23.320 --> 23:27.480] if you don't have time to optimize, you just leave it as it, and you get a crappy port with [23:27.480 --> 23:33.920] slow frame rate. But I did the effort to at least make this part in JavaScript. Welcome. [23:33.920 --> 23:43.800] So next question. He's going to ask more if you don't, so he's ready. Go for it. [23:43.800 --> 23:51.520] Yeah, another question is, what is the size of the wasn't files or whatever that needs to be [23:51.520 --> 24:01.200] downloaded to play this game? It's big. Let me show you. I don't know. It's like this. It's a big [24:01.200 --> 24:12.360] file. It's a big file. So let me inspect. When you go to application here, you see the storage, [24:12.360 --> 24:19.600] you have like 21 megabytes. It's a big file. It's not huge. It's not like Windows when you [24:19.600 --> 24:24.400] start on desktop. It has a little bit of overhead, but it's not downloaded each time, you know, [24:24.400 --> 24:35.840] maybe the first time. Yeah, that's a good question. So we can have one last question. And in the [24:35.840 --> 24:41.120] meantime, while people are still thinking, please don't stay on the edge of the lines, [24:41.120 --> 24:46.280] because people are standing in the back. People arrive a couple of minutes later. So if you are [24:46.280 --> 24:52.800] at the edge, if you're here, and there is an empty seat, you need to shift just a bit. [24:52.800 --> 24:59.640] And you can also optimize this by making a service worker. I did it, but it doesn't work [24:59.640 --> 25:09.760] anymore. But you can also make this as a service worker. And now if you're here, if you see me [25:09.760 --> 25:14.880] looking at you, please shift a little bit. I don't do this for the pleasure of annoying you. It's [25:14.880 --> 25:18.120] because there are people who are going to enter the room. We're going to have more and more [25:18.120 --> 25:24.160] people, hopefully, who are going to ask written questions. So then please let them sit next to [25:24.160 --> 25:30.360] you. Also, there is a trash right there. So when you exit the room, and if you see something, [25:30.360 --> 25:50.240] Evan, if it's not yours, please pick it up. There is another trash there. Thank you. Thank you.