67 comments

[ 3.8 ms ] story [ 70.9 ms ] thread
Bevy is aware of the dynamic plugin issue and solves it by removing the functionality in 0.15:

```rust /// Errors that can occur when loading a dynamic plugin #[derive(Debug, Error)] #[deprecated( since = "0.14.0", note = "The current dynamic plugin system is unsound and will be removed in 0.15." )] ```

Source: https://github.com/bevyengine/bevy/blob/main/crates/bevy_dyn...

That sure sounds like throwing the baby out with the bathwater? If they had something better to replace it, ok, but according to other comments, there will be no replacement...
If it is broke, and you can't fix it, then it's a loaded footgun and should be removed.
It wasn't really used. It'll likely re-appear in a future release in a better state if people want it. Bevy is still pre-1.0.
Ouch. I did not know that Bevy worked that way, with their very own typing system.

I use Rend3/WGPU/Vulkan, which use Rust more or less correctly. My own code is 100% safe Rust. I'm not clear on why so many people have problems with working in safe Rust.

It's not as grim as the author makes it sound... I regularly read bevy code.

Sure there's some custom reflection and type erasure, but that's mostly to support the highly ergonomic APIs and for optimising ECS layout and access.

There are conversations (maybe even an RFC) around better supporting reflection in the language itself, which would improve the situation a lot. (Edit: some of it linked at the end of the post)

Bevy is quite literally pushing Rust festures and the type system to their limits on multiple fronts, sometimes "exceeding" those limits with custom code. All with the intent of not sacrificing performance or happy path ergonomics. Of course there's going to be some unsafe.

> maybe even an RFC

I really should try to pull together my proposal for back references. A big problem in Rust is that if A owns B, B can't have a static reference to A. If you want to do that, you have to make all links to A be Rc references, and have B contain a Rc::Weak reference to A. Then there's a run-time check if you try to make the weak reference into a strong reference temporarily. Usually, the program is set to panic if that fails.

The question is whether a static analysis can determine that such a weak to strong raise will never fail. If that could be proven at compile time, the reference counting would be unnecessary. And Rust could have sound back-references.

If all the places where a weak link is raised to a strong link are bounded by scopes, this is probably do-able. But hard.

Surely there are options other than Python, GDScript, C, C++, C# to consider before making a new language? E.g. GDScript is characterized as "GDScript has minimal tooling. Most of Rust's best lints and static analysis features are missing": well, a new language is going to miss all tooling. And how long did it take Rust to get to the point where it has its best lints and static analysis features?

OCaml could have done fine in that comparison, based on the critique provided. Also, I haven't tried Zig, but based on what I've read, I do believe it would have been worth an evaluation as well.

D for example, used by Remedy Games on some of their titles.

"D: Using an Emerging Language in Quantum Break"

https://www.gdcvault.com/play/1023819/D-Using-an-Emerging-La...

> used by Remedy Games on some of their titles.

You're implying present tense, but from what I remember, the studio gave up on D instantly after Ethan Watson (the guy who gave that talk) left. There have been no mention of D in gamedev in years.

From English point of view, that sentece goes both ways.

"used by" => in the past

"some of their titles." => 1 or more

And yes, C# was won out over D, in what concens AAA studios.

But that wasn't the point, rather languages missing from OP's list.

Bevy, the leading game engine for Rust, seems to have significant issues with the borrow checker and UI toolkit. Its heavy use of type erasure and runtime errors compromises Rust's safety features. These guys suggest that a new language similar to Rust, like June, could address these problems while retaining Rust's strengths. To improve Rust for game development, the community should advocate for features like compile-time reflection, better const support, and improved handling of the borrow checker.

Historically, this situation reminds me of what happened with C++, where continuous fixes led to a convoluted structure that many developers abhor.

> the community should advocate for features like compile-time reflection, better const support, and improved handling of the borrow checker

It's happening :)

When designing anything like a language or library you always create the use cases first and then work backwards. Any game engine not created side by side with a game will be crap. Any 'cool feature' programming language without a decently sized codebase so serve as a beacon to guide you will expose all the flaws in the language that the creators never anticipated because they never created anything sufficiently complex.

Unless you are dogfooding whatever you create, it will be crap and you are better off not creating in the first place, don't waste everyone's time with stuff you yourself couldn't be bothered to actually use.

My take is, if you’re not dogfooding what you’ve created, you either don’t believe in it or you didn’t have a need for it (i.e., likely a solution for a nonexistent problem). The latter is okay for fun and/or research/experimentation purposes, but at least make that clear so that everyone knows what to expect (or not expect).
By that heuristic, only astronauts should be making space suits?
I don't think that's the proper interpretation. It isn't so much that you have to make everything you use, but rather, what you do make you should use.
So the guys making spacesuits should be astronauts?
They don't need to be an astronaut to wear the prototypes and check how they fit, how they allow you to move or not move, how convenient it is to access the tool belt, etc.
Hmm, maybe. But using a product in anger is very different from just kicking the tires.
They do need to to test it in space or in a rocket taking off/landing. Otherwise they're just running unit tests.
The equivalent would be just wearing a helmet or a sleeve. If they are wearing the whole suite they are end to end testing in staging opposed to production.
They don’t have to be astronauts in the same way as as the programmer of a word processor does not have to work in office administration. But if said programmer has to write a letter and uses a different word processor you would raise an eye brow.

I think it definitely would be a good idea if the creators of a space suit wear it and try to do some light DIY with it.

Maybe.

I'm not sure. I can imagine that some programmer would prefer LaTeX, even if they work in the Google Docs team (or the Microsoft Office team). That wouldn't raise much of an eye brow from me: Google Docs isn't exactly aimed at programmers.

I read it more as "spacesuit designers should test the space suits on themselves as well"
Sure, the manufacturers of anything need to test the thing they're manufacturing. But testing something is not the same thing as dogfooding it.
> if you’re not dogfooding what you’ve created, you either don’t believe in it or you didn’t have a need for it (i.e., likely a solution for a nonexistent problem)

That entirely depends on what sort of thing you're programming. My current job is programming for industrial equipment. I certainly don't, and won't ever, personally have a use for the software I write, but that software is 100% addressing a real problem.

Remind me again which games were developed in house to dog-food Gambyro, Unity, Unreal 5, and Godot?
Unreal was designed for the Unreal games, the others I have no clue but I doubt Unity has had any guiding game from my experience using it. Unity was built with the unity-store as its guide imho.
UE5 is almost a from scratch rewrite and Epic didn't dog-food it.
No language fits all use cases no matter how much you want it to be true. I'm a fan of Rust and the trend to rewrite things in Rust because usually you get something much faster, safer, and something that reflects current needs (helix, ripgrep, fd, yazi, zoxide, ...) bla bla but if you want Bevy's level of dynamicness (is that a word?), Rust might not be the language to use.
Or consider julia for the game as you can start with dynamic types and JIT to get quick results and go static (even compile to cpp in the future) with https://discourse.julialang.org/t/syslabcc-suzhou-tongyuans-... later.

See discussions like https://discourse.julialang.org/t/rust-vs-c-and-vs-julia-and... https://discourse.julialang.org/t/should-we-make-a-push-into...

In the future is always a hard path. Maybe does not fully happen.
Yes. I tend to trust their team considering previous libraries they wrote. And if people in the game industry are interested, it might fully happen faster because the added incentive.
What I want for gamedev is not another systems programming language (C++ is good enough for low-level engine code even with all its warts, and it doesn't seem Rust/Zig/etc will replace this easily).

What I really want is something like a lightweight embeddable C# without the GC:

- A strongly-typed scripting language that's easy to bind with a C++-based game engine core (or Rust/Zig/whatever)

- Guarantees sufficient-enough performance while achieving fast compilation for development builds (very important for gamedev iteration). A possible solution for this would be a bytecode VM, but with the option for AOT compilation when exporting your game.

- Comes without a GC. Options could be to just use plain old reference counting, but also something like what Inko does with runtime-checked dangling references or alternatively what Vale does with with generational references.

- [EDIT] Support for value types (like structs in C#)! Really indispensible when you write any "mathy" gamedev code or trying to bind to C++ data structures efficiently.

What is the showstopper for GC? There has been a lot of development in the area, e.g. Java's ZGC has maximum pause faster than 1 millisecond (while the typical pause is much shorter). Wouldn't that be good enough for game scripting?
The problem with GC for a game engine is not just plain performance... but it's when you're trying to bind engine objects to the scripting language. From the scripting side you're dealing with GC-like semantics (where object deletion can be deferred indefinitely), but the C++-engine side objects need to adhere to C++ object semantics (whether that be manual / RAII / reference counting). This gap between the two can lead to some "interesting" engine decisions: for example in Unity they have a separate UnityEngine.Object base type separate from the C# runtime System.Object type, which have very different semantics and thus lead to some funny headaches! Note that Godot's scripting language (GDScript) doesn't use GC and instead just uses reference counting implemented in plain C++, and thus were able to unify the object behavior between C++ and the scripting layer.

An alternative approach for supporting GC in your game engine, would be to extend C++ on the engine-side to support GC with some black magic (aka Unreal's approach with UObject). But that requires adding an additional hacky transpiler / reflection system on top of C++... which I think has been mostly a net negative in terms of usability (if you've tried using Unreal you'll quickly get what I mean...)

Not having GC means either of two things: manual memory management, which contradicts high level nature, or reference counting which is much worse than GC especially in multi-threaded scenarios. Either of the two can use affine memory management hints ala Rust but you still have the issue of sharing the data, which is frequently required in games.

Ever allocated an arena, bumped its pointer for many small allocations and then deallocated the entire thing, efficiently? That’s what Gen0 of .NET’s GC is, and it wouldn’t be an issue in a game.

Good point... but since you've mentioned that in games you frequently need long-lived shared objects, wouldn't these all go towards Gen1/Gen2 instead of Gen0? I'm not worried about any "local" allocations in a non-GC scripting language since I think these can mainly be taken care of by using RAII (or maybe some explicit support for arena-backed allocations, if you really want more performance), but I'm more worried about objects that live more than a single frame.

(Also there's another sibling comment I've wrote that deals with the subtleties of trying to bind a scripting language with a C++ engine core...)

Yes, long-lived objects will go to Gen1 and 2, but by nature of being long-lived they are also allocated much less frequently.

I think it's important to understand the context of where the discourse on "GC in games" comes from - I mentioned in a sibling comment that a lot of perception, at least from Unity side, comes from the fact that in Unity the GC performs (or had performed in the past) really poorly. And, as another comment mentioned, games written in another popular GC language, Java, have little to no avenue in avoiding allocations where even integer operations can lead to data being heap allocated, and requiring the compiler to do a lot of heavy lifting to avoid doing so with escape analysis and packing integers into (compressed) pointer shadows.

Actually, I don't think there is much widespread knowledge on how non-Unity C# performs on its own as a language for both the engine and the scripts, at least here on HN. There are Stride3D (rename from Xenko), Fna, MonoGame (which does not use Mono unlike the name suggests), and probably other I don't know about which use .NET 6/8. I'm not aware of GC related issues for any of these games beyond regular aspects of memory management in games that apply regardless of the platform.

Something to look for is Unity's migration away from their custom Mono fork to CoreCLR .NET as it should eliminate most if not all remaining GC related issues. I find this interesting as .NET's GC implementations (both WKS, SRV and SRV+DATAS) have not been made with games in mind, and a more game-optimized and frame-pacing-aware GC implementation could be definitely provided, at the cost of complexity of doing such project.

I have always wondered why people, especially game developers, have such a hatred of GC. As I understand it, GC has a higher throughput than malloc/free. This comes at the cost of pauses, but that is essentially just quantising overhead into discrete chunks. In the context of a video game, I always thought you could design a GC that runs once at the end of every frame and thereby causes no stuttering at all and gets to completely ignore all objects allocated and discarded during the frame. A performance win, no?

I've only ever come to two conclusions. The first is that the cost of garbage collection is much higher than I estimate, but I have seen no empirical proof of that, and many game engines seem to use garbage collected scripting languages without issue. The second conclusion is that Java's lack of value-types means every program needs to allocate small objects like mad and that degrades performance, which has made most people view GC as slower than it would be if you took a C++ program and swapped the calls to new out with calls to some garbage collected allocator. Do you have any insight on this? What profiling have you done to conclude GC performs worse?

A lot of gamedevs have been burned by Unity's Boehm GC (I assume the situation has improved in recent years? Please let me know.).

Keep in mind that outside of Minecraft, Java is not popular for writing games where the high-ish level language of choice is usually C# instead, both with Unity and many other open or closed source engines, that either host it as a gamescript language or, lately, implemented entirely in it.

> C# instead, both with Unity and many other open or closed source engines, that either host it as a gamescript language or, lately, implemented entirely in it.

And this is what confuses me. The opinions of game developers on this seems utterly baseless, and contradicted by the actual practice of the people who write game engines. I've only ever seen one (quite frankly genius) paper[1] on this that explores the question in a satisfying manner, and it suggests that garbage collection has something like a 17% overhead compared with a malloc call, at the cost of 3 times the memory usage. Not great, but it seems like a video game can afford to triple its memory usage given how it works with resources, and also given that a lot of what's allocated will be large assets which won't need to be counted in the 3× overhead since the represent a small number of objects compared to their size.

[1] Matthew Hertz and Emery D. Berger. 2005. Quantifying the performance of garbage collection vs. explicit memory management. SIGPLAN Not. 40, 10 (October 2005), 313–326. https://doi.org/10.1145/1103845.1094836

What do you mean by baseless? I strongly suggest to avoid such papers (unless they are recent and cover the impact of GC strategy as a whole vs how it is implemented) as they are likely to be worse than misleading, because in the area of memory management, a lot of performance lives and dies by implementation details.

I tend to treat negatively such academic works because they become outdated fast, and are hard to draw conclusions from.

Allocation in competent GC is explicitly cheaper (it's a 1-2 dereferences, and a pointer bump if allocation context/quantum/pool can satisfy the allocation without asking for more memory for the GC itself or without performing collection), you pay the cost later with mark and sweep, and pause time, but most of all, non-deterministic nature of "batch deallocation" of such kind.

> What do you mean by baseless?

They say "garbage collection is slow" and when I ask how they know, they have no response.

> they are likely to be worse than misleading

> they become outdated fast, and are hard to draw conclusions from.

Out of interest, how many papers have you read and how have you come to these conclusions? What empirical evidence to you have for these claims? I don't dispute that there is a general lack of research around this area, but that hardly justifies discarding the scientific method. Given that many of them implement techniques which can simply be re-run as new runtimes are released, why do we not do this? With proper CI, you could easily keep this information as up-to-date as necessary.

> Allocation in competent GC is explicitly cheaper

I am well aware. When I say "allocation", I refer to the entire lifetime of an object. As in both its allocation, residence in memory, and cleanup. Did you read the paper I cited? It provides a strong case that this "lifetime-cost" of an object can be quantified and measured.

> Did you read the paper I cited? It provides a strong case that this "lifetime-cost" of an object can be quantified and measured.

I will read it in full, thanks.

I'm sure part of it is just "conventional wisdom" at this point.

I agree that you likely could design and implement an effective garbage collection-based memory allocator for game (engine) use cases. I am certain many successful titles have shipped with something you could call garbage collection.

In my experience, the expected friction / hatred stems from the idea of a (language-level) GC that you as the developer have little to no control over. i.e. it's not about a direct performance cost/penalty so much as 1: a limiting factor or overhead (e.g. if you don't know which frame the penalty occurs you need to reserve time for it in every frame) and 2: the cost of dealing with yet another layer of main loop system synchronisation issues.

The point about value types is relevant, but probably not strictly a GC issue, as you say. Fast value types are absolutely crucial to high performance game (engine) code. Value types to a game developer are stack-allocated. Going to the heap is completely out of the question. Here, "fast" means performance comparable to the basic numeric types.

(comment deleted)
I've been thinking a lot about embedded scripting lately, to the extent that I've decided to roll my own language. I agree with all your points, but here are some of my own thoughts:

It should strive to be familiar for users of the host language, not invent weird new syntax. This is ultimately a personal preference, but what I want is a script-oriented rust: Dispense with some ugly stuff that makes it hard to compile or analyze (borrow checker, macros (maybe), giant core+std libs), but keep enough of the things that make it good (inferred static types, expression oriented, pattern matching).

Regarding the "no GC" point: My idea is to just not store any persistent state inside the script engine. Allocate temporary objects in arenas which only live for the duration of a top-level call. If you want persistent state, expose it through bindings. This has the added benefit of making hot reloading trivially easy -- when there's no state, there's nothing for your hot reloading system to break. There are some pretty obvious pitfalls of this. It could totally blow up in my face, so the door is open to adding optional global state and/or garbage collection down the line.

I have a very(!) minimal prototype, and I've written enough of a compiler before that I'm confident I can eventually produce something that works. Whether it lives up to my aspirations is a different story.

If you want something that actually exists now, maybe look into AngelScript. I haven't personally used it, but it's statically typed and supposedly has good C++ integration.

Yup I am actually using AngelScript in my current project since it’s currently the least worst option for me! Having the smoothest experience binding with my C++ engine so far.

I also started a side project making a strongly-typed scripting language compiler / VM a few months ago, but it’s currently on hold since I’m trying to not do too much yak shaving. But maybe someday…

> Compile-time reflection would help a lot with almost all crates. That's both for their compile times and feature set. (goodnight, sweet prince)

It's silly that people uncritically accept the narrative that all hopes of getting reflection implemented were lost when JeanHeyd quit the project.

As far as I'm aware, the only thing JeanHeyd posted was an initial design exploration. Not a formal RFC, not code, just some design theory-crafting. That's good, that's something people need to do to advance the language, but it's a little off-putting how some people are treating that writeup like some Graal lost and not like one design exploration among many of its kind.

Especially since that design work would likely have stayed purely theoretical for a while, given that the compiler's team reaction to a similar proposal, variadic generics, was "We won't have the bandwidth to even evaluate the proposal for at least a year".

someone got a Rust error in console when tried to run Rust game?
C++ is for the engine IMHO. It can do a lot of things without as many twists as Rust.
It really shocked me that it took people so long to figure this out. When I used Rust, I found it really quite unpleasant for prototyping, which if I understand it correctly is essentially all you are doing during game development. I imagine someone who stuck at it for a long time would learn a lot of strange lessons very quickly and eventually start churning out a lot of very odd code, I know I started to do that after a while. Indeed some of the stuff in this article reminds me of the stuff I used to make. When you want to make a programming language work in ways it just isn't designed for, you end up making a lot of smelly code. I see this in C++, so I'm not entirely surprised it shows up in Rust also. Rust is designed by C++ programmers to service their needs so it logically also replicates the shortcomings of C++.

Use a high level language for your logic, and a low level one to optimise your hot loop. Have we not learned this lesson already? It is almost always easier to design something at a high level and migrate parts of it downwards as necessary.

I kind of agree. It is pleasant to use for prototyping in "simpler" use cases where you want correctness from the start. I use Rust daily for implementation of IETF standards and drafts and rust helps me a lot in figuring out undefined cases or things that are still unclear.

I would never use Rust for game design or reactive UI and I don't understand why people are trying to shoehorn Rust into these use cases. They both benefit immensely from global shared state and object-oriented patterns and Rust's correctness makes workarounds harder if not impossible.

> When you want to make a programming language work in ways it just isn't designed for, you end up making a lot of smelly code.

That depends on the language. If your language is flexible/multi-paradigmatic, it was designed for you to make it work in all sorts of ways, so there is at least a potential of the code not eing smelly.

> Rust is designed by C++ programmers to service their needs

Rust was originally written in OCaml for someone who wanted to work on a browser engine. So, I'm not quite sure about your characterization. Remember also that different (C++) programmers have vastly different needs.

https://en.wikipedia.org/wiki/Rust_(programming_language)

This makes sense, and explains why there's more Rust game engines than Rust games :)

Rust adds friction to quick prototyping, but OTOH it is particularly good at providing robust, safe library APIs (with machine-checked memory management, lifetimes, sharing across threads, and strong patterns for error handling). It's one of the reasons why Rust applications can easily use so many dependencies.

Rust intentionally makes it difficult to cut corners, and by default enforces all of its best practices around immutability, thread-safety, etc. This is annoying for quickly trying things out for gameplay purposes (central point of the "Leaving Rust gamedev" post), because exploration of gameplay ideas doesn't need bug-free code, it needs to iterate quickly.

But this focus on correctness is the right trade-off when building foundational components and libraries. Applications need to have a stable and reliable platform to build on, and to actually ship they can't have a "prototype-quality" engine.