Hi, everyone! Hi, Fosdame. How are you doing? I hope that you're doing great. Yeah! Let's
go! So I'm really honored to present my talk about how I ported Doom to the browser with
Blazor Wazen. So it's a topic about another language, which is, we will see. So a quick
word about myself. So I'm Yessin Ben-Abbas. I'm a DevRel at Wordline. I'm a teacher also.
And during my spare time, I love to play video games. So that's one of the things that made me
make this port. But before going further, let me explain what is a port. So game porting consists
of making a game run in another platform other than the original one. There are many ports that
are released nowadays. Some are good, some are bad, depending on how they are developed. And it
consists of adapting the source code of the original game into the new platform. So adapting means
that maybe we need to change some bits of the code. Maybe sometimes it can be a whole rewrite,
depending on the differences between the platforms and how the game has been developed. And using
a virtual machine or an emulator is not really considered as porting. You really need to have
access to the source code and adapt it to the new target platform. So in the beginning, I wasn't
really confident with making a port. I considered it as a complex task, difficult. I didn't have a
clear vision on what it is. And what really gave me a first inspiration to consider porting games
is modern vintage gamer. Who knows about MVG? Yes. So he's a game developer, a YouTuber who makes
great videos. I didn't expect it, but he made a video where he showed how to port Heart of Darkness,
one of the greatest games of retro gaming. And he showed how he ported it to the original Xbox.
So he made a video where he showed changing includes. So watching this video really made me
more confident and considering porting in one of my activities. But that's not the only thing
that gave me the idea of porting a game. The other thing is I like to play with.NET framework.
I really like this framework. Because it has many good things. Some of them is that it's an
open source, cross platform, general purpose framework. So it runs on Linux, Windows, Mac,
Android, iOS, a lot of platforms. And even other platforms that I'll talk about now. And the language,
the main language of this framework is C-Sharp. So C-Sharp is really, really good language that keeps
evolving over the years. It has some features that you can find in modern languages, null safety,
extension functions, and this kind of stuff. So it really keeps evolving great language. However,
in the beginning of the framework, the browser was not target. But in 2020, with the release of.NET
5,.NET introduced the support, the Blazor wasm framework. So it's a component-based framework,
like Angular, React, and view with components. But the code of the component is in C-Sharp. And it
runs locally, natively on the browser, thanks to a WebAssembly stack, as you can see here. So Blazor
has WebAssembly implementation, which allows the developer to access Razor components and also
access the.NET framework, and also communicate with the DOM. So this is an example of a Razor
component. It's similar to what you see in Angular or View. The difference is that the code below is
in C-Sharp. In addition to that, you can have also CSS, of course, and you can even call JavaScript
and it interoperates with C-Sharp. So when I saw this, C-Sharp.js, which interoperates, I was like
this. I was amazed. I was really happy to see this. And I told myself, it's time to make this port.
So now I need to find a game to port. There are many games with source code available. And the
game that I chose, no spoiler here, it's Doom. So I will tell you next why I chose Doom. But one
of the reasons is that it's one of the most successful first-person shooters. And not footers,
shooters, sorry, for the stick. And also, technically speaking, it's well developed, in my
opinion, because the logic of the game is separate from the resources. So you have the famous WAD
files of Doom. So it contains the assets of the game. And you have really what updates the game
state, the position of the character, or the game logic in a separate project. So that allows
to have Doom being portable by design. And in terms of ports, Doom has a lot of them in video game
consoles, of course, and even anything that has a screen and some processing, as you can see here.
And there are even more. So comes the reason why I chose Doom, because I found that there is a
.NET port already existing of Doom, of Linux Doom, which is the source code released by
id Software. And in GitHub, there is a repository which has developed a port of Doom in.NET.
However, this port uses libraries that communicate with hardware, like graphics, audio input,
which are not compatible with the browser. So that's why my work was to take this port of Doom
and make it work on the browser. So just to be clear, I used the V1 of Managed Doom,
because currently they are developing a V2, which uses another library, but just to be
clear on my work. So to summarize, id Software released the source code of Doom for Linux.
Since you developed Managed Doom, which targets any platform that is targeted by SFML, desktops
mostly. And this is what I intervene to base my work on this port and make it work on the browser.
So before starting work, my porting, I made a strategy which is this one. So this is an AI image,
by the way. I tape Doom monster typing on keyboard, and I got this. So my porting strategy was to
get something that works like proof of concept that works quickly and to demonstrate quickly. So
the first step is to take the source code and compile it with the Blazor framework, as simple as
it is. And as soon as I see a compilation error, I delete the code and I add the to-do. So another
presentation with to-dos, that's fine. So after that, once the code compiles, I replaced little by
little bits of code that are not implemented, or the methods or functions that are not implemented,
by giving priority to frame rendering, because it's always nice to see something on the screen,
rather than working blindly. And in terms of optimization, I always left that to later, unless
it's really necessary. And in terms of reading documentation, so it's really well documented,
how Doom is implemented, but I only read the parts which are really relevant and important,
specifically how the Doom image is drawn on the screen, when the frame data is generated by the
engine. And with this kind of porting strategy, like two, three weeks of part-time or site project
work, I was able to achieve something, a port that can be run, executed, even if it's not perfect
yet, but we'll see in the demo later how it works. Now let's enter into more details on how I ported
more concretely this into the browser. So first of all, before giving more further explanations,
let me show you how game is developed most of the time. So it's a big picture of the game
algorithm. First of all, we have a wide loop, which is an infinite loop, but it doesn't iterate as
soon as possible. It iterates only when the frame-pacing is relevant. For example, if you have
30 FPS game, this next iteration will wait a little bit if the previous frame was computed
very quickly. So it allows to have a frame-pacing which is correct and nice to the eye for the user.
So once the frame is ready, we get the user input compared to his previous frame. Really simple.
And after that, we run a frame or we compute the next frame of the game. So we run the update
game state. It's just an example name of the method. It takes the input of the user, the
what file for the doom in this case. And then it advances the game one frame. So it updates the
player position, the monster position, the ammo, the status, his life, all this kind of stuff.
And it also generates to be rendered a frame and some audio. And this is run. So for each frame,
this algorithm is run and it updates the game each frame. And once we get a frame and some
audio, we play them and render them to the user. So when you see this, you can start guessing
which parts. So in the managed doom, this all is done in C-sharp. That's clear. And you can start
to see which parts need to be adapted for the browser, which are not available in C-sharp,
but need to go to the JavaScript realm to be able to achieve it. But to show you what I
ported more precisely, let me show this in another way. So here. So we have the while loop and the
frame-pacing step here. Next, the user input is sent to the update game state with the what
file as argument. And then we generate some audio and the frame to be rendered. And it loops.
So this is what needs to be ported, what you see in red. So what you see on the top is frame
pacing. It's not really Blazor relevant, but browser relevant. For a frame pacing,
there is a better way to base frame in JavaScript, base frames in JavaScript. And to render,
since SFML is not available in Blazor, so this needs to be replaced. Also the update game states
which is also, so everything is in C-sharp. Update game state, even though it's a platform
agnostic, it's not 100% the case. So there needs to be some bits that needed to be adapted to the
browser. But hopefully 70% of the code approximately was across platform and runs on the browser
without any problem. So after some work, some coding, some fun, some fails and learning,
I achieved this result. So I replaced the white loop with request animation frame. Anyone knows
about request animation frame here? Yes. Nice. So yeah, request animation frame is how you tell
the browser, so I want to render frames in an optimized manner for the browser. For example,
when you switch a tab, don't do anything to optimize energy. So you ask the browser, request a
new frame. When you see it relevant to compute a new frame for my game, call me back. So it's a
callback. And for each frame, we call it back. After that, so once this has been changed,
change also the rendering. So for the audio, it's the audio context library. And for rendering,
it's the canvas, of course. So audio context is the audio API of the browser. But there is one
thing that I didn't mention yet, and that you see here, is that in this state, since I was,
as I said earlier, Blazor is a component-based framework. It's like Angular View React. You
need to have some kind of main component, which is the entry point of your program or of your
component. So here it's missing. So that's why I added or I had to have a Blazor component,
which only serves as the entry point to invoke the JavaScript, which then goes back to C sharp.
So this is C sharp.net, C sharp. So when I say C sharp.net, I say them interchangeably. So this
is JavaScript. We go back to C sharp. We go back to JavaScript. So there is a lot of context
switching or language switching. And this is achieved thanks to this API. So Blazor provides
an API that allows to go back and forth from a language to the other. So this is Blazor way
of doing things before.NET 7. Starting.NET 7, there is even a better way to do this. I'll show
it at the end of the presentation. Okay. So now we have something that runs. So I will show you,
quickly show you for the audio parts, some code, and then I will continue the last,
or maybe just the entry point and then continue the presentation. So this is the main component.
As you can see here, which in the code, so we have the canvas here. And here we have,
we initialize the DOM object or the game object. And then here we invoke the JavaScript method
that calls request animation frame. So we invoke the JavaScript method here. We here handle the
frame pacing. Okay. Here we handle the frame pacing. And then we call back.NET to run an iteration
of the game, the DOM engine to run a computer frame. And then we call request animation frame to
prepare for the next frame. Which calls back this method. So this is like an infinite loop.
And this method that you see here, which invokes.NET code, just invokes the game objects and
requests it to render a new, to compute a new frame with the user input. So this,
I will just skip it. So this is how audio and video are rendered. So it's communication between
C sharp and JavaScript. And I continue. So, so what I learned from this in Blazor, avoid
copying arrays, big arrays. In the beginning, in the.managed DOM source code, the final image is
generated by copying, converting a one-dimensional array into a 2D array. So this slowed down the
game a lot, a lot, a lot. So I removed this part from the managed DOM source code. And I sent it
to JavaScript. That's what you, what I was, was shown in the previous slide about frame rendering.
And I don't have to cover it. But yeah, avoid copying big arrays in.NET. This is in.NET 5.
Maybe in.NET 7 it has been improved. Avoid extensive logging. And calling Blazor from
JavaScript from Blazor communication can be very fast if you use the correct API. As I said,
however, this, the API that I used is undocumented. And I confirm it because I found no documentation,
just some source code or some obscure GitHub repositories. But hopefully in.NET 7 it's,
it's improved. In JavaScript, I learned that request animation frame is the way to paste frames.
And to play audio programatically, you need to have some user interaction before or the
audio context API doesn't work. So here is the demo. So I click the, to enable the audio,
the user interaction. And let's see. Yes, of course. Yes. And here we go. So just to show
you that we have sound. So don't be afraid. It's just a game. And just to show you that you also
have secret passages. I don't know if you know this one. You have 200 armor. But that's another
topic. And it runs correct frames. It's a 2012 Macbook and it runs at 30 FPS. No problem. Okay.
So last two slides, the interrupt in.NET 7. So here's how interrupt works now. You don't need
Blazor. It means you don't need to create component if you want to interact between JavaScript and
.NET anymore. And I'm working currently on this part because it's really exciting to see this
kind of work. So to call JavaScript methods from.NET, you just need to export your JavaScript
method as you do in any JavaScript module. And you call here, you just import the method and you
can have access to it. And in the opposite sense, you just export your.NET method and then you
import it in JavaScript using this kind of code. And that's it. So I'm working on changing how the
game is ported to this. And in terms of next step, then, it's to migrate to JS interupt. Update to
manage Doom V2. Maybe I will gain some more performance. After that, I would like to have some
game music and also to be able to play other wads. Currently, only the Doom one works. I don't know
yet why. And as long-term, really, it's also a wish. Maybe this can be integrated to the official
managed Doom project. So as a conclusion, wasm makes existing code compatible with browser. It
means that, I mean, wasm is not just the very fast JavaScript alternative. It also opens the way
to make many, many languages, many, many technologies run on the browser. So that's really what I
like. What's really exciting for me, at least about wasm, and porting games is fun. Developing is
fun. Do you agree? Yes. Thank you very much. Thank you. So we have time for a couple of questions.
Who wants to ask the first question? Thank you. Hi. First thanks a lot. It was really, really
insightful. Thank you. I have a question about request animation frame. I think I saw, so,
because request animation runs at 60 FPS, right? And then I saw you do something with
timestamps to try to do 30. Yes. Does it ever drop or become inaccurate? Or is it just,
is that like the right way to achieve 30 FPS? Yeah, I guess it's, maybe, I'm not a JavaScript
specialist on this. Me neither. I'm curious. It's here, I guess. Yes, it's here. For me,
it worked. Yes, I didn't have eyes here. That's what I found on the Internet. I tried it. And
I've seen the demo. It doesn't drop. When it drops, it's really when there is a lot of things
happening. When there is a lot of audio, it's still not optimized a lot, the audio part. But this
frame-pacing, for me, it works. So you compute the duration between the last request frame and
the new request frame. So for me, it's okay. Excellent. Thank you very much. You're welcome.
Next question. Yeah. The next speaker who is speaking after? Nobody? Nobody is speaking
after you? I mean, who is taking that seat who are standing there? Okay, we'll call them.
So it's a follow-up to the previous question, actually. Have you tried removing this check
and see how fast you could run the game? Can you do 1,000 FPS? No, it's not 1,000. No. I actually
tried to remove, but don't remember, but it's not 1,000 FPS. For sure, it's certain. It's not
really, really fast also. Like maybe to 40, 50 FPS, it depends also on the machine. It depends
on the hardware that you have. On the processor hardware that I have, I don't have a gaming
computer. It was like maybe 40, 50 FPS. Okay, thanks. But yeah, that's a good question because
you see when we talk about good ports, bad ports, for example, this, I mean, it's a quick to achieve
port, but it's not the most optimized one. So that's when you see game companies making ports.
And also, for example, when I said that the array copy on.NET is slow. So at the same time,
if you don't have time to optimize, you just leave it as it, and you get a crappy port with
slow frame rate. But I did the effort to at least make this part in JavaScript. Welcome.
So next question. He's going to ask more if you don't, so he's ready. Go for it.
Yeah, another question is, what is the size of the wasn't files or whatever that needs to be
downloaded to play this game? It's big. Let me show you. I don't know. It's like this. It's a big
file. It's a big file. So let me inspect. When you go to application here, you see the storage,
you have like 21 megabytes. It's a big file. It's not huge. It's not like Windows when you
start on desktop. It has a little bit of overhead, but it's not downloaded each time, you know,
maybe the first time. Yeah, that's a good question. So we can have one last question. And in the
meantime, while people are still thinking, please don't stay on the edge of the lines,
because people are standing in the back. People arrive a couple of minutes later. So if you are
at the edge, if you're here, and there is an empty seat, you need to shift just a bit.
And you can also optimize this by making a service worker. I did it, but it doesn't work
anymore. But you can also make this as a service worker. And now if you're here, if you see me
looking at you, please shift a little bit. I don't do this for the pleasure of annoying you. It's
because there are people who are going to enter the room. We're going to have more and more
people, hopefully, who are going to ask written questions. So then please let them sit next to
you. Also, there is a trash right there. So when you exit the room, and if you see something,
Evan, if it's not yours, please pick it up. There is another trash there. Thank you. Thank you.