KBall catches up with Florian Rival about bring a C++ based game engine to the web by compiling to WebAssembly and creating a React-based frontend.
DigitalOcean – The simplest cloud platform for developers and teams Whether you’re running one virtual machine or ten thousand, makes managing your infrastructure too easy. Get started for free with a $50 credit. Learn more at do.co/changelog.
Keen – Keen makes customer-facing metrics simple. It’s the platform that gives you powerful in-product analytics fast with minimal development time. Go to Keen.io/JSParty and get your first 30-days of Keen for free.
Fastly – Our bandwidth partner. Fastly powers fast, secure, and scalable digital experiences. Move beyond your content delivery network to their powerful edge cloud platform. Learn more at fastly.com.
- React Native
- C++ Standard Library
- Create React App
- Babel Macros
- Native Web Apps: React and WebAssembly to Rewrite Native Apps (video)
- Native Web Apps: React and WebAssembly to Rewrite Native Apps (slides)
Click here to listen along while you enjoy the transcript. 🎧
Alright, hello! This is Kball here, reporting live from React Amsterdam, in Amsterdam. I am here with Florian Rival, who is a software engineer at Facebook and has developed an open source gaming engine using React and Web Assembly. Florian…
Hi, everyone. Nice to be here.
Yeah, thank you for joining me. I’m really excited to hear more about the game engine. But first - you’re speaking a little later today…
Can you give us a little bit about what your talk is gonna be?
Yeah, sure. My talk is about using React and Web Assembly to create applications that are going a bit beyond what we used to do. The idea is that I had this game engine that you just mentioned, called GDevelop. It was a whole C++ game engine, desktop application that you can use on Windows, macOS, Linux, and I was like “Okay, maybe I could port it to the browsers, and have a kind of refreshed version…” Because I’d been using React for quite a bit of time, and I was like “React is a really good way of making interface, so is there a way I can remake the software in a better way, using React?” That’s how I happened to use Web Assembly to in fact port most of the software from C++ to Web Assembly.
My talk is basically about this, and what was the change using Web Assembly, and what are the things that we can use in the React ecosystem to make ambitious applications like a game editor.
So is the core engine still written in C++, but you’re now compiling it to Web Assembly?
Yes, there are the core classes of the software that describe what the game is - the objects that are in a game, and the rules that define the game. The interesting thing about the software is that people can create their own game without programming, because they are able to create rules of that game using visual events. It’s a bit visual programming, in a way. And all of this is still in C++, because there’s lots of business logic that I didn’t want to rewrite - all the tooling to convert your game from vis structure and memory to a real game.
That’s neat. Can you give me the spoiler? What did you have to change to get that to work?
The user interface of the software at the beginning was all done in C++, using a cross-platform toolkit called wxWidgets. Also, cross-platform toolkits like this in C++, for example Qt. My idea was “Can I remove this user interface from the codebase?”, so I had to dig a bit in the C++ code to basically remove all the classes that were defining the interface, just to keep the core classes, the business logic describing what the game is and all the tooling around it. And once you have it, you are then able to run Emscripten (the compiler), but instead of at the end having an executable that you can run on your machine, you can run it as a Web Assembly module.
Yes, that’s the theory. In practice, there are a few things that you have to know, of course. Once you have your whole codebase that is converted to Web Assembly - that’s a very good start. You can actually see in the browser, for example when you log things in the console, it’s actually redirected to the console in Chrome, so you can see that things are running. So it’s a very good first step. You can use Emscripten, like I did, but if you want to write Web Assembly from scratch, you can use a language like Rust, or this interesting project called AssemblyScript, which is basically a kind of TypeScript that compiles to Web Assembly. So it’s very interesting. I mean, there are multiple ways of writing Web Assembly. In my case, I had an already existing codebase, so I wanted to reuse it.
Were there any major gotchas along the way?
I will say that the first thing that you will see, that the bundle that is created, the Web Assembly module is quite large. It depends on your codebase, of course, but even for something that is quite small, if your code is, for example, going from C++, you have the standard library that is coming along, so it weighs a lot. For example, for GDevelop it’s 3 MB, the bundle containing Web Assembly code.
[00:08:11.04] Yeah. Well, and Web Assembly megabytes are cheaper, in some ways. They’re the same amount over the wire, but parsing cost goes way down.
Yeah. And again, it depends on what you’re making. If you’re making a complex game or app, it might be okay to ask the user to download this bundle. And also, as I’m packaging the application as an Electron application, 3 MB more or less - that’s okay. So yeah, that’s the first gotcha.
You raise a really interesting point… A lot of times we think about Web Assembly, “Oh, I’m gonna run it on the browser, and it’s gonna be there”, but part of this for you, it sounds like, was just you wanted to use React for your UI, even if you’re shipping it in Electron…
Being able to have that seamless integration.
But still, I was really interested in React, because I’ve been using React Native for making applications, and React for making websites, or kind of applications on the web… And I was like “We can do things that are really impressive with React, so let’s try to port the whole interface to React and see how it goes.”
It’s interesting to think about this, because React Native, as you bring up, is sort of trying to do a similar thing. “Let’s use the React abstractions and ideas for native programming.” Here we’re taking something that wasn’t originally planned for mobile or anything like that at all, and saying “You know what, it doesn’t matter. We don’t have to build it from scratch with this. We can just take out the UI component.”
Yeah, yeah. I think that’s the approach of React Native, but binding to native components. Here I’m doing a bit the reverse - I’m staying with React.js because I’m primarily targeting desktop users, so it’s fine to run React.js and the performance is correct. But I’m still binding to existing native code that I don’t want to rewrite, and I want to reuse. So in my case, I’m really using a codebase that I did, but you could do the same with, for example, existing libraries that are doing computation, like a physics engine. I know that some have been compiled to Web Assembly. Codebases of game engines, of course, but also things like maybe physics simulation… I think that we’ll see more and more people using Web Assembly modules inside applications without even us seeing it.
Yeah, yeah. In my talk I’m speaking about Web Assembly for the first part, and then moving to more React-related stuff, but that really could be another framework. The cool thing with React is that it has already a huge ecosystem. In the second part of my talk I more or less explain all the packages and open source modules that I’ve been using in React to make an interface that looks like a native interface.
[00:12:03.03] For example, a list of Android [unintelligible 00:12:03.03] and if there are performance issues, how to deal with them, and other things like displaying trees of nodes… For example, in my software, the events that are describing the rules of the game - it’s basically a tree that is displayed on screen… So how to do it properly with DOM elements in React. But all these things could be applied to another framework.
Yeah, that makes a ton of sense. Were there any things that you found were missing, coming in into Web Assembly? I know for example the Web Assembly is making a big push - or the Web Assembly Coalition - towards being able to do multi-threading, and scripting across that… Was that something that proved to be a problem, not having those features?
So how do you track those things down? What were the tooling that you had to apply in addition to just Emscripten to compile?
The first thing – I already had a set of tests in C++, but not enough, if you ask me… [laughs]
The universal developer situation, right? “Oh yeah, we have testing… Not enough…”
The first time, you’re like “There should be a good chance that it’s gonna crash at some point”, so I started to write this, and basically what I’ve been doing without knowing is I created a set of tests on the interface of my library - because at the end, what I have is a library…
…and this thing is giving you great confidence in the fact that it’s working, and also, if later something is crashing, you already have a test and you knew that “Okay, the base case is working, so maybe I’ve been misusing something, but it’s working. So it’s on me to fix it, it’s not on Web Assembly.”
Yeah, exactly. And I’ve been looking at things to automate the creation flow types, or TypeScript types for the library that is generated… It’s still not a thing. I’ve seen a project called Nbind, that allowed to compile your C++ codebase to asm.js; so it’s still not Web Assembly, but they are making automatic generation of typing… So I’m really missing Vis; I hope that we’ll see more and more tooling creating Vis types.
Yes. And speaking of types, I’m a big fan of a typing system like TypeScript or Flow. It turns out that it’s not really necessary when you’re starting, but as the app is growing, it’s really convenient to have typing that helps you to refactor; it will help you or someone else (a contributor or another teammate), and in fact, when you’re back to your codebase after a few months without dealing with it, you are a stranger to your own code, and types will save your life. Or at least it’s telling you “You’ve removed a prop in this component, but you’ve not done it in the rest of the codebase.” So it’s already powerful tooling, that I guess is really shining a few weeks or months after you’re writing the code.
Yeah, yeah. It’s the type of thing that it doesn’t feel like you need it when you’re getting started, and then as complexity grows, as contributors grow, you really wish you’d started it from the beginning.
Yeah. Sometimes I’m back in some components of the user interface that are not typed, and I’m like “What was I thinking?!” I mean, it’s okay, it’s working well, but it’s a good thing to add typing and to have the peace of mind that things will be alright.
I think the important thing to think about when you’re using Web Assembly with C++, might be better if Web Assembly is getting garbage-collected at some point. For others it’s not the case. So I think the most important thing is to make sure that you understand the lifetime of your objects.
At some point, for example, I created a new Web Assembly object, then I deleted it at some point, and without seeing it, I was reusing it at some other point… So it was a crash, again.
That’s the thing you want to look at when you’re starting, especially if you’re coming from a more Web background.
Is there any tooling available for debugging those memory leaks? I know on the native side there are lots of different tools that folks use… Have any of those things been ported to Web Assembly?
I’m not sure… I’ve not seen any tooling like this. Hopefully, that will appear. I’ve seen people – when you compile your Web Assembly module with some debugging flags, you’re getting source maps. For example, you can see in your Chrome debugger the source in C++…
…and you can go from one line to the other. That’s really awesome. In my case, as I said, I have quite a lot of tests, so I’m actually confident in the fact that it’s working. But if you are writing from scratch, it’s a good idea to see if it’s working, at least to see your worst Assembly script or your C++ codebase in Chrome. That’s really fun.
[00:24:05.18] Yeah, right. I think that there is no good solution for now, except maybe automatic garbage collection. Basically, you can’t really test for manual memory management. You just have to be careful.
Yeah, absolutely. So you talked a little bit – for the game engine your target is laptop, desktop, things like that. Is this also a methodology that will work for targeting mobile applications?
I think that could be… If you’re running a native application and you want to use your native codebase… Well, if it’s C++, or even Rust maybe - I’ve not tried - you might as well compile to a native library, and reuse it in your iOS or Android application. I will say it’s a bit the same in React Native. If you want to reuse a native library, you can keep it as a native module… But I think that this is getting interesting for mobiles for progressive web apps, for example.
There is an example made by some Google developers - it’s called Squoosh.app. It’s a PWA, but running Web Assembly code to reduce the size of an image, and to do transformation on an image… And it’s open source, so it’s a good example for people that want to start.
I think that we’ll see more and more applications - well, web apps, and even web apps for mobile - that are running some kind of Web Assembly. And using this, we might get something that is not as fast as a native app on mobile, because native still has a lot of compiling advantage when it comes to making user interface; it’s super-smooth, and so on… But you never know – with Web Assembly running your business logic, or maybe some part of your interface, that might get really smooth and good enough to say that it’s an app… And it’s not a progressive web app or a web app, it’s just an app.
Anything, yup. The strength is that you can Npm-install anything. The weakness is you can Npm-install anything. [laughter]
Yeah, absolutely. When you were talking about the size of the Web Assembly, pulling in the standard library… Is there any concept of tree shaking when you talk about compiling? We’ve got this standard library, but maybe I’m only using five functions. And sure, they use 20 more underneath the covers, but 25 out of however many thousand…
Yeah… So there is no code-splitting or tree shaking. Or in a way, there is. For example, what a language like C++ and compilers have been doing for some time is that when you’re compiling your whole software and using a library, only the functions that are actually used will be included in the binary at the end. So it’s basically tree shaking.
They’re already doing dead code elimination, or whatever it’s called.
I think dead code elimination might still not be 100% exact, so you’re still having more libraries than you want to have. I’ve seen things like O2 having some kind of dynamic libraries in Web Assembly. That means that you could have your native code that is required only when it’s really needed.
I don’t know, for example, if you have a physics engine that you want to reuse, if it’s a 2D or 3D there might be a way to exclude the 2D library or the 3D library, according to what you’re doing.
You have to get a good balance between enough innovation in your ecosystem and something that is robust enough. People like to say, for example, when the package left-pad was removed from Npm, that was the end of the world. In a way, it was, but I think that still it’s not a problem about the ecosystem, it’s a problem about the thing that the package will be immutable, and it shouldn’t be able to be removed maybe…
Yeah. You can Npm-publish something in a few minutes. If you like for example React, when you have your tree of components, you have a large component, it’s easy to take a bit of JSXMware, extract it to a new component, and reuse it really quickly.
I think this feedback loop that is really quick is important in the whole stage of the development, including in libraries. If you want to make a new C++ library, that takes a bit of time to get the whole tooling set up; that will be a nightmare, if you compare it to Npm. I hope that things that are compiling to Web Assembly, like Rust, are improving this - the ability to create libraries really quickly. Because that’s how you create an ecosystem that is exploding, instead of growing linearly.
[00:32:00.13] Yeah. We seem to be figuring out some of the factors that make that possible, I think. An emphasis on refactorability and composability is huge. That was one of the driving - at least stated - motivations for hooks; it makes it easier to cut and paste code, and refactor into new locations, and move things around.
I was speaking about typing… I think it’s a bit the same. When you’re investing a bit in some tooling like this for making this easier to refactor, then it’s a huge win. Some people told me “Yeah, but you know, if you make small modules, you will have things that have a simple interface, so you don’t need typing, for example.” That may be true, but on the other hand, you can’t say that you will never refactor something. Even a small module, you’ll want to, at some point, add or remove something. Even components – a strength of React is to be able to move components easily, and I think something that you want to keep is your ability to refactor things without breaking things… So that’s why I think typing and having a library that allows you to create a component using only a function - that’s a really good thing.
Yeah. And anytime you are exploring somebody else’s module, having those types is really useful… Because I don’t know, am I using this right? Oh, it didn’t compile. The types are wrong? Okay. Now I know what I need to do.
Yeah. That’s basically documentation, and it’s a safety net. It’s particularly useful for a library. This being said, it depends on your use case. I have the website of GDevelop, the game engine, which is done in React, using Gatsby, but I have no typing, because there I don’t really care. The model of components that I have with React is enough to get something that is working well. This being said, I’m more or less the only one to be working on it, so I might change my opinion if I get more contributors.
Yeah. Okay, I wanna explore that a little bit, because we just talked with Jason from Gatsby… So we were hearing a lot about what they have working, and sort of the inside view, but you’re coming and you’re using it as a user. What’s your impression of Gatsby?
I’ve been very happy with it. I’m a big fan of the React approach, because when I was redesigning the website, I was like “I can identify components in the design that I want for my website.” I wanted to have an easy way to start a website, so I started to look at Gatsby and all the performance that comes with it. I’ve been very happy with it. The website is running really fast, and the development experience is really nice, because there is auto-reloading, which is something that’s a bit hard to set up; but it comes for free with Gatsby. So yeah, I only have actually good things to say about Gatsby and the ecosystem around it.
Yeah. It’s funny thinking about, these days, with Webpack and various things… We almost take for granted – okay, auto-reloading; I make change, it’s just gonna be there… But that’s a phenomenal upgrade in productivity, because of that iteration speed.
Yeah, yeah. That’s, again, the feedback loop in the development, that is really important, that is being improved by this. That’s the reason why I ported my software interface to React, because I can use Storybook, auto-reloading – well, I’m not using auto-reloading, but at least things like Storybook to develop the components in isolation. That’s a huge speed improvement.
To come back to Gatsby, I think that it’s making out of the box most of what I wanted from my website… So I went with it, and the thing that I like - if at some point I want to scale the website more, I’m not afraid, because it’s based on React. I know that there is an ecosystem around it, I know that if I want to add, for example, a part of the website where you have to be signed up, you can do it, because at the end it’s only React. And still, it’s server-side rendered, so I get something that is blazing fast.
Yeah. It is amazing how fast Gatsby sites are… And they’re doing a lot more than just the server-side rendering there – or sorry, a lot of the pre-rendering. They’re really emphasizing “How do we optimize this to make it super fast?”
I wanna swing back now, because we were talking about the importance of auto-reload, and that kind of fast iteration… When you’re working in your C++ codebase, is there a way to hook it up, so that you get automatic recompile and changing, or how does that end up working?
At some point I’d like to have the compiler running after every change… That would be possible, basically. The compilation - there is a package.json that is running Emscripten and compiling the C++ to do a Web Assembly module… So I could more or less do my own watcher for files and rerun it every time I’m modifying something.
Right now, my feedback loop is changing something in the C++ codebase. I’ve been using VS Code. That has a good integration actually with C++, so I can even get the errors directly in VS Code. So that’s the first thing that is important - get the errors displayed in your editor. In C++ you can’t get errors at compilation; that takes your whole terminal… So having a good IDE to start is, I guess, the main thing.
Then I have a terminal, I run the npm run build, then the test just after, and when the tests are passing, it’s copied to the non-modules of my React application. At this moment, as there was a change, the app is reloading.
It’s still longer than I would like to, but it’s not that bad, because I remember doing a change in C++, then recompiling the whole thing for a few seconds when you [unintelligible 00:39:13.24] then running the native application, going to the screen where you want to test, and see that “Oh no, I made a silly mistake. Let’s start again.”
Now I have less Vis because as my C++ code is more or less my business logic, I can test it faster using unit tests… And the interface is done in React; I can use something like Storybook, and have my component displayed directly. So I’m much faster at writing components in the interface, and I’m equally as fast as writing C++ for business logic.
Yeah, you kind of get the best of both worlds there.
Yeah. I guess that’s also okay with the gotcha that I told - you get the best of both worlds; that’s what I want to show in my talk. I’m not saying that you will have a great experience all the time, but that’s something that is working. That’s working.
Yeah. Talking about the build steps - it sounds like you have a manual build still, but I’ve seen people do Webpack integration, essentially pulling in C++ or Rust stuff as modules, directly into Webpack.
I haven’t looked at the newer versions of Create React App, because I’ve been more in the Vue ecosystem recently… Do they still require you to eject to customize the Webpack config, or do they use the Webpack Compose?
I love this. I used to do a lot of curating these domain-specific languages using Ruby, but there it’s all runtime, and it’s Ruby, which is slower. Here we can do it at compile time using Babel, and it’s lightning-fast. Rust has this idea of cost-free abstractions - that’s basically what this is. It has a cost, but only at compile time.
Yeah, that’s great. In a way, the template metaprogramming in C++ was the same idea, but it’s super-easy to use, so most of the time I’m avoiding to do it on my own codebase. It’s super-convenient for libraries, but also a pain to use… And a pain to debug. [laughter]
Yeah. It sounds like “a pain to debug” is kind of a theme as we go along; if you’re starting from the web, and you’re used to the tooling that’s available on the web… It’s funny, because we used to say – console log debugging became a thing because the tools were so bad. Now the tools on the Web are so good that anytime you kind of go away from it, you’re like “Wow, I miss my…”
You mean I come at a breakpoint without recompiling? That’s strange. But this being said, if you’ve never tried for example native development on mobile, the experience is pretty good if you start Xcode, or Android Studio. Good things have to be picked up from both worlds.
Yeah, absolutely. Awesome. Anything else come into mind you wanna chat about before we wrap up?
Not much… Just check out GDevelop. It’s my game engine, and it’s for anyone; because of the visual programming system, anyone can jump into software and start making games, so make sure to check it out.
And it’s all open source, right?
It’s all open source.
Awesome. Thank you so much, Florian.
Thanks. Thanks a lot.
Our transcripts are open source on GitHub. Improvements are welcome. 💚