Could a game engine like Roblox be built with Rust?

I’m asking because it feels like the design of Rust prevents many patterns from being properly implemented in the language. UIs, which are heavily OOP and mutable, are nearly impossible with Rust unless the programmer completely rethinks how to build one. Knowing this, I’m wondering if a game engine like Roblox, which is very heavily based on OOP design principles and freely mutable tree/hierarchy structures, could be properly implemented in Rust.

Well it depends on which parts of OOP you're referring to.

If you're referring specifically to inheritance (which Rust doesn't have), then the answer is an unambiguous "yes". The reason for that is that any inheritance hierarchy can be (and is better off being) implemented by means of composition:

struct Base {
  // shared properties go here 
} 

struct Sub {
  base: Base, 
  // properties that would go in a Java/C# subclass go here 
} 

Rust has ad-hoc polymorphism by the mechanism of traits, so that wouldn't be a problem either.

However, w.r.t. leveraging OOP for GUIs and games, applying that approach directly won't work in Rust.

So ultimate conclusion? Yes such games can be written in Rust, but they'd require a bit a of a rethink on the game engine level, and they need to migrate away from an OOP mindset (which would be a good idea in its own right).
The good news is that exactly that seems to be happening, as ECS engines written in Rust are becoming more plentiful. I fully expect that that model is powerful enough to write something like Roblox.

2 Likes

As someone who works on UI in games on a daily basis, I'd say this only applies to certain implementations. You can have a composition and event based UI that doesn't need inheritance and direct mutation. React and Elm from the web world are based on this principle, and so are many of the Rust frameworks, as far as I have seen.

5 Likes

If you're running user code, you should be treating it as untrusted, so your engine implementation language basically doesn't matter.

Any off the shelf scripting engine from v8 to .net to custom "rusty" languages should work fine for exposing an API for people to build their projects on.

So why should the underlying engine be Rust? Well if you're trying to consistently provide both performance and safety, it's currently pretty much the only game in town right now.

3 Likes

In terms of model you're absolutely correct. And it will quite likely work fine for most 2D and 3D isometric games, as well as 3D games with relatively simple graphics and game state.

But I'm not convinced that such architectures can scale to AAA games in terms of performance, due to garbage pressure at larger scales from working with the immutable data structures. So it really is a matter of "what precisely is the use case?".

You wouldn't copy or use something like React 1:1, but use a similar "display, receive input, update state" feedback loop. That's what decouples the reading and writing in a way that should work well with the borrow checker. With indexes/IDs to avoid storing shared references, you can use the classic data structures. A UI is inherently interconnected to some extent, but it doesn't have to be a plate of mutable pointer spaghetti.

1 Like

For some prior art in this area, see godot-rust. Godot uses a class-oriented ECS to describe game state to the engine; the Rust bindings bridge the gap between a class-oriented type system and Rust in some creative (and largely successful) ways.

A Rust-first game engine likely wouldn't use a class-based system in the first place; Rust's emphasis on composition suggests that a system designed to be extended in Rust from the start would likely also put its emphasis on compositional approaches. This is common enough in Rust-based ECS frameworks today.

2 Likes

If you look at how much UE tortures C++ to get what it wants -- it parses its own custom annotation language atop any type you want it to know about and codegens a bunch of stuff in the background, without which most of its features don't work -- I have every expectation that a AAA engine that wanted to could torture Rust at least as much and have it be possible to do whatever.

Obviously I wouldn't recommend that, and would encourage anyone working on Rust to use a different architecture instead. After all, the things you need to do to actually be able to use more CPU cores in any language start looking more like what Rust wants anyway. (DOTS in Unity, for example, is far more rusty than C#-OOP-y.)

But "prevents" certainly feels overstated.

7 Likes

Whats with all these "Rust cant do UI" comments as of late ? :sweat_smile: Its fine, theres nothing inheritely OOP about UI, its just the way things developed over time. Rust strict mutation requirements aren't that big of a deal either, in fact, at least in the web frameworks world the trend has been to have stricter and more deliberate mutation patterns so things don't become a mess. To be fair, I have no experience with game development, I cant really see what the issue is.

4 Likes

no its not. and by safety you actually mean "a subset of safety issues considered to be pertinent by whomever".

Correct, it is best to be specific, although it is well known: Memory safety plus freedom from data races.

1 Like

The keyword was "consistently", and "trying to" was also doing some work: without the guarantees Rust does ensure, all the other things you might want are far harder to provide too, and all of the (many also nice, don't get me wrong) other languages out there don't provide both consistent memory, thread, and multiple other types of technical safety and consistent performance.

The whole paragraph was an aside addressing that the main argument I was making could seemingly imply that you shouldn't use Rust at all for games, when it actually has some strong points for the part that isn't user-provided that you can't get elsewhere - the point wasn't to prove that Rust was perfect.

2 Likes

I haven't replied to this thread in a while, but I feel the need to clarify some things.

I think my first mistake was assuming that everyone here knows how Roblox works, so I should probably explain it.
In Roblox, nearly everything is either an Instance or a ServiceProvider. Both are abstract C++ classes which define fields and behaviour common to all Instances and Services, respectively. Instances are like the individual "nodes" of a scene, while Services are singletons that can contain instances as their children and provide useful methods to the Lua(u) scripting API.
The DataModel is an object responsible for managing all of the Instances and Services in a Place (I believe it can also serliase/deserialise place and model files, which are XML representations of the DataModel). The game global in Roblox scripts is a reference to the DataModel the script is running in.
Roblox uses Lua(u) as its scripting language. It provides an API that allows developers to freely modify the DataModel (by adding/removing Instances, changing their properties, etc.), request Services using game:GetService(), hook into both engine-provided and scriptable events, and so on.
Instances can be modified by changing their properties, but also by adding Instances like SpecialMesh or Decal as their children (SpecialMesh changes the mesh of its parent, while Decal applies a texture to a specific face of its parent). Parts, physics joints, user-created meshes/textures, and most other "physical" objects in Roblox are represented by Instances.
The DataModel can be freely modified by scripts or through the Studio interface. Anything that can be done through a script while the game is running can also be done in Studio (although some Studio functions require special permissions to be used in scripts). The properties of Instances and their positions in the hierarchy can be changed at any time, and Instances can be created or destroyed at any time as well.

From my experience, this sort of architecture is nearly impossible to implement in Rust. The language is very strict about mutability and does not allow fields to be inherited. Using serliased fields in place of regular struct fields could probably solve this problem, but it seems like bad practice.
It feels like these restrictions would make it impossible to build a proper scripting API on par with that of Roblox.
To make a proper implementation of a Roblox-like engine, you would have to rely heavily on abstract classes, inheritance, and serialsed fields that are bound to class fields (that also have to be fully mutable for scripting and editing). I just don't think that's possible in Rust.

It MIGHT be possible to create a virtual representation of the DataModel that can be freely scripted and edited without mutating the original, but that seems very clunky and overcomplicated.

1 Like

I feel like I should also describe the relationship between the Roblox engine and its scripting API. In the C++ part of Roblox, the Instance and ServiceProvider classes use private fields that cannot be accessed without getters or setters. This is standard in most C++ game engines. I believe that Roblox also uses macros (or a Property/Descriptor class, I'm not sure) to define which fields are serialised and/or exposed to the editor/scripting API. When Studio or a script reads or changes a field, the appropriate getter/setter method will be called internally.

This pattern might be practical to implement in Rust, but I'm really not sure.

And one last thing:
In my opinion, the main thing stopping a project like this from being viable in Rust is the ecosystem. C++ developers have access to Irrlicht, OGRE, G3D, and many other 3D graphics libraries. There are also more libraries for networking, physics, etc. that heavily simplify game engine development.

Rust just isn't there yet. The most popular graphics library seems to be WGPU, which is designed for the web, and is also very low-level compared to something like Irrlicht. Very little gamedev middleware is created for Rust.

Everything you described seems pretty straightforward to implement in Rust, other than inheritance, which I personally can do without with no problems. I didn't try building it though, so I'll take you word for it.

I linked several scripting engine bindings earlier, they have exactly the same actual restrictions on use as in C++, it's just enforced by the language.

This normally looks like the runtime providing a single threaded context that your bound instances need to be owned by, and then rules about runtime reentrance, eg script -> host -> script is inherently unsafe as you can't stop the script calling back into the same host object. Generally the solution is either a block on calling back into the same object that shows up as a script error, or an event loop that serializes (and introduces parallelism!) all accesses.

In C++ you get a line in the documentation, in Rust you just can't write it (or you need to use unsafe, or the library is broken)

Now, implementing these language bindings is hard, sure, but using them isn't generally that hard. Certainly a lot less expertise needed than in C++

Regarding the suitability of Rust for the engine; yes and no - certainly raw Vulkan is more painful than in C++, but it started from awful, so that's not saying much. If you don't like WGPU, that's fine, you've got access to every native graphics API and plenty of popular wrappers too - but I wouldn't dismiss WGPU just because it's related to WebGPU! It's still a bit raw the last I used it (WGSL errors in particular), but it's got a clean design that maps well to modern APIs portably across OSs and hardware types - there's a lot of value there, especially for mod-focused engines.

Obviously there's not a whole lot of full game engines with editors and all the bells and whistles implemented (yet!) but it sounds like you're closer to wanting to write one of those, not use one. The engines that do exist are very likely more than good enough to get you going; bevy (the Rust game engine darling) has multiple packages implementing scripting support you could start with.

2 Likes

Since I've written something like Roblox in Rust, a client for Second Life, I can say that bunabyte is completely correct. There are no major obstacles to using Rust itself for game or metaverse work. But the graphics stack is weak.

Performance is poor by game standards. WGPU tries to target Android, WebAssembly, Windows (Vulkan, OpenGL, or DX12), Linux (Vulkan) and MacOS (Metal). It works, but there are performance problems. High-performance features of Vulkan, such as bindless and multiple queues, are lost with WGPU. So big, rapidly changing scenes are sluggish. Worse, WGPU has become slower in recent releases. Most recent WGPU version dropped performance 21% and added jank. The developers are focused on targeting web browsers, and don't worry too much about poor performance on desktop. They're annoyed at me for pointing this out.

Then there's the next level up - the renderer. The renderer handles lighting and shadows and manages GPU memory. There's Rend3 (abandoned, but sort of works), Renderling (still in alpha and funding runs out at the end of 2025), and a few attempts to write My First Renderer and Learn Rust. Bevy has their own renderer, and it's apparently OK, but not usable outside of Bevy or particularly fast. They go through the WGPU bottleneck.

At least three major game projects have publicly abandoned Rust. There is, as far as I know, no Rust game with large, complex scenes and fast performance other than one sailing simulator that targets "good old DX11" directly.

It's been this bad for the last four or five years. Despite much hype. the graphics stack hasn't improved much.

I have some hope for Renderling, but they're stuck at the WGPU roadblock. The WGPU problems are fixable, but that group is focusing on tracking the twists and turns of the WebGPU API, as their main focus is web page graphics.

Fixing all this needs maybe five people for a year. Nobody wants a high performance Rust graphics stack badly enough to make that happen. If you only need low performance, you may as well use OpenGL, which is mature, stable, and runs everywhere.

3 Likes

I'm not sure if Tiny Glade counts here on either rendering with Rust (it's Bevy, but from what I can tell they may not be using Rust for rendering) or having "large complex scenes" - certainly they're fancy enough to impress Digital Foundry, but the Tiny is in the name.

Tiny Glade is a neat little game. Very good ideas there.

It's not pushing the limits of rendering, though. The Rust graphics stack start to stall out somewhere between 10,000 and 25,000 meshes. You can do Tiny Glade, but not GTA or Assassin's Creed or Cyberpunk 2077 sized worlds. If you try to swap out textures, content, impostors, and levels of detail at a high rate, the lack of concurrency stalls things out.

There are many 2D games in Rust, but you could have done most of them in Flash a decade ago, or HTML/JavaScript now. The heavy machinery of Rust really starts to pay off on big projects with internal concurrency. That's when the graphics stack falls down.

1 Like