> A serious C++ engine with a "data oriented" design would have the same arrays of primitive types and dumb structs as its C counterpart, merely dressed as std::vector or std::array
Yeah, I agree. Including the "full C++" to the comparison was just trying to avoid having to argue about which is the "right" subset of C++ for engines, which is somewhat besides the point. The main problems I have with modern, data-oriented C++ are mostly compile times, slow debug builds and non-trivial reflection.
Absolutely you should take the text with a grain of salt. The reasons I chose C are very personal.
I can entertain the idea that my desire for simplicity is just a fad. I suspect that I wouldn't move to something like Python (I use it for other purposes though), because I don't like optimizing. I want a mindset which helps producing code with reasonable productivity, and of which performance I don't need to worry much. But yeah, it's possible that I'll change my mind and write an article about how I was so wrong before :P
Good that I succeeded at avoiding ranting. I find it really hard to keep from throwing absolutes.
Almost everything is possible with C++, that's true. Some things require a lot of engineering though. Like full program reflection, fast debug builds, and fast (below 5s) builds.
Funny that you mention the decoupling of state and logic, because I see that as a non-issue in C. State is a struct, logic is a function. There would need to be a really convincing argument to make me wrap the hundreds of game object components I'll have to two objects each.
C has some differing cognitive cost, which C++ doesn't have, that's for sure. I think we have just differing personal preferences on which is worse :P (There are real-world situations where I'd choose C++ though)
The network point is valid. Implementing it was mostly a nice learning opportunity. I haven't yet decided if I want to keep it or not. It currently lacks safety and proper compression, so if I get serious about it I'll probably make all network data go through validation functions, which helps with both compression and handling hostile data.
>Take a look at the games made in Unity. Plenty of AAA or close to AAA games. Cities Skylines, Firewatch, Ori and the Blind Forest, Pollen, Endless Legend.
I think we may have differing standards. I've only played one of those, Ori, and it had massive problems with long frames. Like, often, and sometimes even half a second long pauses during normal gameplay. I don't know if Unity is to blame for that, but that game is not a good argument in favor of it at least.
Two reasons: Creative interests which make traditional game engines a hard fit. I could make compromises to the design, but as I also have technical interest in making game engines, it's a double win.
The need for high performance meets with my creative interests. (But I also have technical interest in making the engine). I also value the ability of being able to implement extraordinary features to the engine in a whim, which is hard with large general use engines.
I agree that there is a class of games that can be easily implemented with a prebuilt engine, but this is not one of them.
Author here. I'm positively surprised, constructive discussion on internet!
There's been some discussion about using Rust.
Rust is an interesting language, and has definitely potential substituting C and C++ in some domains. The main reason I'm not so interested in using it is for game development, is because it's more complicated than C (like C++), and the complications are a bit off from what I'd want (like in C++). Just a quick googling reveals that rust mangles names by default, and doesn't have reflection, so I'd probably be in for a lot of negative surprises. Add that with being indifferent about the absolute security, and soon my code is mostly inside unsafe blocks so that I don't have to spend time convincing the compiler my pointers are safe. Maybe. I haven't done much programming in Rust. There are some nice convenience features though, when comparing to C, but they seem to be rather minor things.
It comes down to choosing between two non-ideal solutions. I value simplicity more than some, so favoring the simpler one feels more natural to me. Sure, when you need the safety then Rust seems like a decent choice.
There's a lot of tricks you can do to optimize build times, and I've tried most of them in my previous game engine. Got ~90kloc recompilation time from 15min to something like a few minutes, although that required rewriting a bunch of code to not use templates. And removing boost. Multiple days' work.
But the point is, that I'd like a language that is fast to compile by default. C seems pretty promising, as doing a full unity build of my 25kloc codebase takes something like 3 seconds. Not sure how much of that is actually compilation, and how much IO. I'm expecting the project to grow to something like 100-200kloc, and hopefully never have to spend time figuring out why the compilation takes too long, and instead use that time to do something productive.
Maybe, I don't know. This is just me trying to minimize the unnecessary pain and suffering while waiting for a better language to arrive. In that context arguing which bad solution is less worse seems somewhat pointless, and also depends on personal taste. I'll have to see this project through to know better.
I started thinking about it, but quite fast decided to roll my own self-contained system. Not because it was an informed decision, but felt more fun :P
Compile times are not a problem in small codebases. Try to compile a 100kloc codebase using templates generously. Even your link times will easily grow over 10 seconds. Maybe even a minute. And 100kloc is still a pretty small codebase in terms of AAA development.
It's true that complexity somewhat depends on the context. Complexity of a language is a useful metric when talking about mental overhead of the programmer (which translates to productivity), and when writing custom tools for the language, both of which are relevant when developing a game engine. Safety has traditionally been a quite small priority in gamedev. I assumed this was the context.
Yeah, and what's left is not much of C++. See my post about runtime recompiling for arguments to use C compiler instead of a very limited subset of C++.
(References make the type system more complex for little benefit. Templates create massive amounts of complexity and slow down my iteration loop. Already explained what's wrong with std::vector and std::unordered_map.)
When you drop the semantic silliness of C++, like having the container to take care of constructing, copying, moving, and destructing, not to mention exception safety, a basic implementation of a "templated" dynamic array implementation in C comes down to like 100 lines. Hash map will be a bit more, and is not so trivial to write.
It's true that there should be no need to write these things yourself. The alternative C++ gives is not really tempting. A language designed for demanding game development doesn't exist (yet), so one evaluates which is the least worse option.
That's partly true. However, you do get rid of the complexity you never wanted in the first place. For me this is
OOP, RAII, C++ allocators, exceptions, references, vtables (see my post about runtime recompiling), templates (mostly), C++ standard library (I'd have to write my own vector for fast compilation. I'd have to write my own hash map to get contiguous storage (IIRC))
C is by no means optimal, but it's still one of the least bad languages to write games with.
It's quite restricted still. For example changing datatypes during recompilation is problematic, at least if you don't destroy the instances before reloading, and re-instantiate afterwards. I don't bother to do that, because I see most of the value in things like tweaking game object logic repeatedly, which fits the restrictions nicely.
There are some arguments to stick with C instead of a very C-like subset of C++. Off the top of my head:
- Recompiling and reloading parts of your game at run-time is quite easy in C. In C++ you have to make sure (at least) that nobody has pointers to vtables of the dll at the time of reload. This can be a bit tricky if you're using things like std::function in your dll code. Yes, you could be using a scripting language, but thinking how to match the semantics of a scripting language with your engine, where to draw the line, and then write the glue code is a lot more work than just reloading some plain C functions. And if you later decide this was a bad/worthless idea, reverting from dynamic C code to static is almost a no-op, whereas reverting back from scripts is dreadful.
- A quick & dirty reflection is easy when you don't have to deal with name mangling, templates, and overloading. Just some script scanning through your code and outputting elementary type info to .c file may be enough for things like real-time memory browser-editor for your whole engine. This can be very valuable when developing new engine features, as you can view and edit, and maybe draw even graphs of members you just added to some struct. Also useful for modifying game object data on the fly when debugging/creating levels.
Every time I've prototyped something on a GC'd language (java, actionscript, javascript, ..) I soon find myself optimizing memory allocations and not making the game because the GC causes too much overhead and/or causes too long pauses and therefore makes the game feel miserable. It doesn't matter if there are some theoretical GC models which have < 1ms pauses with gigabytes of heap data, as the current implementations don't seem to be anywhere near that.
Nowadays I prefer to use C not because I like malloc, but because it makes handling different memory allocation schemes trivial (unlike C++). Furthermore, in this semi-real-time case the performance of malloc is irrelevant because when you want to be fast and low-latency, you pre-allocate everything and use lightweight functions to further distribute memory from those blocks.
In conclusion, I've found that manual memory management lets me focus more on the task I have in hand while GC don't.
Yeah, I agree. Including the "full C++" to the comparison was just trying to avoid having to argue about which is the "right" subset of C++ for engines, which is somewhat besides the point. The main problems I have with modern, data-oriented C++ are mostly compile times, slow debug builds and non-trivial reflection.
Some don't mind those, and be happy.