Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> - Bevy (Rust ECS engine), which is nice at first but has a lot of problems with its implementation and can become rather messy.

Can you expand a bit about why this was messy or comolicated? I found the paradigm leads to pretty well organised code (sometimes you get the odd large system, but it can be broken down into smaller systems, sub systems or composed out of smaller functions).



I started using Bevy for a small game and abandoned it after a little while.

There are two issues:

1. The fundamental issue is that the ECS model has independent subsystems communicating via a relational database. This breaks the connection between function callers and callees, makes control flow incredibly hard to trace, and means the type system can give you very little assistance.

2. Bevy also does not leverage Rust's type system in other ways. E.g. you can use resources that you forget to create and it will only crash at runtime instead of giving a compile time error.

I think you can create a good game in Bevy, and if you come from a C/C++ world you probably won't notice the lack of type safety. It didn't meet my goals, however.


> 2. Bevy also does not leverage Rust's type system in other ways. E.g. you can use resources that you forget to create and it will only crash at runtime instead of giving a compile time error.

Would something else even be possible in Rust? My understanding is that you cannot really add type safety in the way of "This should be a i64 and between 32 and 128, otherwise fail to compile", so not sure how it could be addressed by the language.

Or taken to the extreme "Fail to compile if the user creates an instance of this but doesn't call function F with that newly created instance"


Let me explain a bit more about "you can use resources that you forget to create and it will only crash at runtime instead of giving a compile time error".

Plugins are the main abstraction in Bevy. A plugin has two parts: build and run. Build creates stuff, and run uses stuff created by build and created by the build of other plugins running at the same time.

Build is pure side-effects, so the result type of build tells you nothing about what it builds. Therefore there are no types that can constrain what resources run uses, and therefore failing to create a resource that is used in run is a run-time, not compile-time error.

The alternative is the build returns a type representing the collection of resources it creates, and run's type is a collection of resources it uses. This requires some type level programming (a type level heterogeneous set). This is some of the simplest type level programming, but type level programming itself is quite foreign to most programmers. I assume this is why the Bevy developers went for the easier to write solution that doesn't enforce constraints at compile-time.

More generally, just like in regular programming some things are easier and some are harder to do in common type systems.

Your first example (integer constrained to a range) is harder because you need to solve linear inequations at compile-time, which is not a feature of most type systems (though see refined types).

You second example (must call a method) is relatively easy with linear types, as they express "must do something with this value".


> You second example (must call a method) is relatively easy with linear types, as they express "must do something with this value".

Interesting, could you possibly share an example on how that would look like in Rust? Haven't come across it yet, and would certainly help with some things.

Lets say we want to make sure if "MyStruct" is ever defined + created, we want to make sure the program somewhere calls "register_struct" with an instance of that struct.


Rust does not implement true linear types, only affine, so I believe your parent is mistaken.

You can do a dynamic check to encode this, but that's not super popular. You can also issue a warning, and in theory turn that warning into an error, but it may also trigger on code unrelated to the specific struct you want it to, so I don't think that's a full solution either, and others may use your code without the warning, getting less guarantees.


While any design requires discipline, ECS systems have little or no coupling between them, and they very easily end up all over the place; in addition to the chaotic design, one ends up also not having an idea of what happens when.

For this reason I agree - developing games using an ECS design requires more discipline to manage complexiy, compared to an imperative one.


I've not had much issues understanding what runs when, because for the most part I didn't need to care that much, and when I did it was possible to order/schedule systems in Bevy. That's even a bit easier nowadays too, since the API improved!

With regards to the chaos: I think it can be avoided, but like you said it requires a bit of discipline. I don't feel like it required that much more than normal software engineering (but I'm also the type of person that documents and tests everything even on personal projects lol). The plug-in system makes it easy to help bring order too.

This is all through a bevy-tinted lens, since I've done very little game dev (dabbled with UE and Godot) outside of bevy, though!


I agree with this - ECS lets you write totally decoupled, composable bits of game logic very easily, which is super powerful. But in my experience if you're not careful you end up with a hundred perfectly decoupled little things and it's really difficult to remember how they actually all come together to make the whole game you've built.

Easily grokable filenames and file structure are very important, and bevy specifically has a pretty nice plugin system which lets you really clearly 100% isolate groups of state/logic into more sensible sections instead of ultimately having some root level game loop that's directly setting up your 100 tiny little poorly named things.


Yeah, I'm one of Bevy's maintainers, and I've seen folks get tangled up (or overengineer things wildly) if they go in without a clear plan. ECS (and a strong compiler) makes things much easier to refactor, but I generally agree that the more flexible nature demands more discipline.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: