C or C++ for my game engine?
crafn.kapsi.fi
crafn.kapsi.fi
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.
EDIT: Re: pointers -- Rust is a lot easier to write if you just ignore pointers. Really. Either pass small structs by value or pass references (either immutable or mutable), and let the compiler handle the actual pointer manipulation.
If it wasn't for the uptake of UNIX, we probably would never had to discuss about memory corruption in 2016, other than writing stuff like device drivers.
There is now a whole generation that thinks C was the very first systems programming language, the compilers were as good on day 1 as they are today and it has became some kind of sacred cow.
http://www.itsecdb.com/oval/definitions/product-47/0/Linux-L...
Regarding memory corruption and games, it is how we get around to achieve game cheats.
Rust to me feels like someone sat down to make a systems language that was actually aware of the last 40 years of programming language development. I don't have to sacrifice expressiveness for performance anymore.
The borrowing and reference safety mechanics are just an extra layer of worry-removal icing on what's already a pretty appealing cake.
I agree with you in the sense that overfocusing on safety has led to things like Andrei's "bulging muscle" criticism, implying that Rust is especially lacking in high-level and metaprogramming features. Compared to C++ and D, it certainly does have a lower feature count in that area (although I'd argue that it's counterbalanced by the fact that Rust holds the line on strong typing for generics and hygiene for macros, whereas C++ and D don't). However, compared to most other languages Rust has very feature-rich generic programming and metaprogramming support. We're not talking "does not support generics" here; we're talking about the difference between the 95% and the 99% metaprogramming use cases. In the overall landscape of industry languages, even just having associated types makes Rust's generics system one of the most sophisticated out there. The only popular languages I can think of with more powerful generics are C++, D, Scala, and Haskell, and the first two sacrifice strong typing.
The main reason for the focus on safety is that it, combined with the lack of GC, is what actually makes Rust unique. Very few industry languages have any features unique to them, but the borrow checker is one such feature. C++ and Swift may get something like it in the future, but Rust has it now, and the entire language and ecosystem is designed around it (and the borrow checker is especially difficult to bolt on to an existing language, because it relies on strong aliasing guarantees). So it's natural that most people have focused on the zero-overhead safety when describing Rust--it's the most salient answer to the question "what can I do with Rust that I can't do with language X?"
* I could be pleasantly surprised, though. In theory, for incremental builds, based on the general design planned [1], rustc should be able to do better even than C in a lot of cases because only changed functions need to be recompiled rather than entire files; but I'm a pessimist and expect there will be something to make it slow in practice. I could be wrong though.
[1] https://github.com/rust-lang/rfcs/blob/master/text/1298-incr...
But I think most of the main points you describe as impossible in C++ are actually completely possible. It means moving away from the 90's paradigm of using C++ to implement deep class hierarchies with design pattern, but after all C++ is a multi-paradigm language. If you can do data-oriented design in C, you could most certainly do it in C++, and the abstractions C++ provides actually make it easier.
In essence, I would separate game objects to a logic instance and state objects (pure structs) and use smart pointers with a generation-counter to point the logic instance, which in turn would have smart a pointer to the state struct. The smart pointer would overload the dereference operator and transparently update the logic instance to get the new vtable if needed.
This decoupling of state and logic could do many nice things, such as serializing the entire game state in a very clean way, and pure state structs would not be harder to parse than C structs (they would essentially be C structs), so you can still have your memory editor.
The main difference between C and C++ here would be the cost abstractions, both cognitive and performance-wise. I have to admit I've never ran into standard abstractions significantly slowing optimized debug code, except for standard library containers. I'm not a game programmer though, so YMMV. I get the cognitive cost argument, but for me the cognitive cost of C (namely having boilerplate noise scattered all over hiding the interesting code and having to be super-extra-careful with memory management) is higher than than the cognitive cost of internalizing all the layers of abstraction in C++.
If you're programming a network game, I still think you'd do your users a better service if you don't dismiss safety offhand. There's a whole class of memory-safety bugs which would never surface during normal play, but could still be exploited with specially crafted packets. Of course, C++ wouldn't give you perfect protection either.
It takes some experience to build applications this way, but it is well worth building that experience. C++ is still my language of choice for anything that requires fine control of memory and attention to performance. Rust looks like a very possible successor, but I agree that the lack of incremental compiling makes it not useful for large projects at the moment.
Not quite. With its destructors (and the RAII that come with them), C++ makes it easy to follow a scoped discipline. Allocations are easily scoped, but they don't necessarily happen on the stack.
Strings and vectors for instance, are typically allocated on the heap, assuming you're using the default allocator (which most people do anyway). While this is convenient and make things simpler, that's quite the runtime overhead, and causes latency problems similar to those of a genuine garbage collector (malloc() and free() aren't exactly constant time).
> never allocate memory in libraries
Now that can reduce the number of allocations in a program. Note that this also rules out most of the STL: its containers call their (possibly user defined) allocator themselves.
And yes, this rules out the STL :-(
And they still are, thank goodness. My point was that as soon as you call a constructor, all bets are off: for instance, merely declaring an std::string allocates on the heap, local variable or no. But it seems you already knew that.
I think it is important to distinguish scoped discipline from stack allocation. The former is a way to program. The later is an implementation detail (modulo performance). Your wording was a tiny bit sloppy, so I jumped at it.
Whoa there. Let's not exaggerate the scope of the problem. Large projects in Rust usually consist of many crates (in Servo, 150+). Rust absolutely has incremental compilation on the level of the crate, so the compilation times are similar to what you see in C++ with per-directory unity builds. This is the same story as Go, for example, and people don't say Go isn't suitable for large projects; incremental compilation isn't even on Go's roadmap.
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.
> Funny that you mention the decoupling of state and logic, because I see that as a non-issue in C.
I beg to differ. Decoupling logic and raw data is not an issue in C, but state is not the same as raw data. You may have the same state data duplicated in memory (e.g. an index), and there's also the issue of representation of state which is not portable (e.g. pointers, particular data structure). You also have pointers to functions (logic) in your data if I understood you right.
With C++ (or any sufficiently high level language) you can easily abstract away the difference between state and raw data. If you've got function pointers or pointers to instances of logic objects (which is a must if you don't want to litter your code with long switch clauses), you could save them as type IDs or something akin to that.
You might be programming something very different, but so far most good C code I've seen was thoroughly objected-oriented. Not in the sense it religiously followed the 3 pillars of OOP, but in the sense it coupled structs with several functions that have been meant to use on solely with these structs and sometimes employed polymorphism by embedding function pointers in these structs.
All of that is much easier to do with C++ classes, and if this is all you wanted (and you don't think you need proper separation of data from bone fide state), you can have a code that would refresh the vtable pointer in memory, and would sure be more straightforward than manually updating function pointers all over the place.
> 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.
Validation is nice, but you can't foresee every weird way your data could be shaped. It's very easy to miss one small corner. That's why you see several security engineers going around these threads dissing C. :)
C++ generally incurs a price on link times too. For example C++'s stronger type system creates more burden on the name mangling, like there is actually a difference between an int and long, even if they are the same size on the system. Then if you have code that doesn't make this distinction, overloaded functions need to be generated which creates more generate code bloat. Then if you throw templates into the mix, C++ generates a ton of symbols for every permutation. This is why C++ object files are usually much larger than C object files and why the linking process is so much slower.
Some interesting ideas otherwise, though.
I also think the "convenience features" are a pretty big deal. Generics with traits are a pretty big value add for their complexity, and to me affine types take away a lot of the complexity of C.
No offense, but I think you suffer from the same problem. You're in love with coding, so being forced to micro-manage things the 'C' way isn't a problem for you.
But at some point in time you have to look back and justify the hours spent. You -will- be more productive by having the majority of your code in C++ 11. Things -will- be easier to maintain for yourself and others.
I have written absolutely brilliant C code that I'm very proud of, but if it's been a while since I looked at the project I have to sit down for an hour and re-familiarize myself with how things work....and I'm the one who wrote it.
Based on what you've written I can tell that you don't have the experience to justify writing the bulk of your code in C. Guys who inline ASM all day long can have a hard time doing that. I know it's harsh, but it's meant as helpful criticism.
One other thing: You don't have to pick just C or just C++. If you have engines in your project that are clearly better in C and you enjoy doing it, more power to you....I encourage you to do that. However, it's very easy to get hung up on C and lost in your own C world of imagined optimizations.
My advice: Start off with solid C++, move parts to C later on when testing justifies it and your amount of free time justifies it.
He makes no mention of inline assembler, nor does he encourage premature optimization. He talks instead about a concrete problem he had: the typical performance of operations was not good enough, thus no single optimization would help much. He also mentions that the abstractions encouraged by C++ do not help you build data-oriented applications (e.g. games); using them properly actually hurts performance.
these things are really codebase readability and maintainability concerns, and to me they're orthogonal to "data-oriented programming", things like optimizing for cache locality, SoA instead of AoS, not calling virtual methods in warm or hot paths, throwing away malloc and using purpose-built custom allocators, SIMD/AVXify all the things, etc. etc.
it's a lot harder to handle the insane, byzantine complexity of a game engine in something as pared-down as C.
with that said, i don't like C++ and i definitely don't think it's a beautiful language by any means.
jonathan blow's new language, jai, is very interesting. purpose-built for game programming. check it out if you don't know about it yet. it's looking really promising for all types of high-performance, game-like projects.
Many C compilers can use normal infix operators with SIMD vectors and it will have better performance than C++ operator overloading (especially in debug builds). All you need is a typedef.
typedef float vec4f __attribute__ ((vector_size (16)));
vec4f a = { 1, 2, 3, 4 }, b = { 5, 6, 7, 8 };
vec4f c = a * (a + b);
The typedef is slightly different for Clang, but that's just one line. This code should work with GCC, Clang and the Intel C compiler. MSVC doesn't do this, but I don't write code for MSVC any more because I can compile compatible object files with Clang.In my experiments, I've noticed that you get the best performance by passing vectors and matrices by value, not by pointer or reference. It also makes the API nice, because you can return matrix values, e.g. `mat4x4f mvp = matrix_product(projection, matrix_product(model, view))`. In some nasty cases you might need to add force_inline attribute, but most of the time the compiler will inline the functions anyway.
In C++ with operator overloading it's easy to do stupid things like in-place addition (e.g. operator+= for a vec4f) or start transposing matrices in place. This will make the compiler emit memory load/store instructions when you'd want to have these values in registers.
> being able to have generic containers with templates
Generic containers are somewhat of an issue with C, but most of the std::containers are not suitable for some game development tasks (this is why projects like eastl exists). I prefer using intrusive containers in C, similar to how the Linux kernel deals with linked lists and red-black trees. Even the C++ standard library (the GNU one) is internally implemented this way, the "generic container" is just a thin type safety shim on top to avoid template bloat.
That sounds like a failure of inlining. If you're at the point where a single stack spill makes a difference, you won't want to pay the cost of the calling convention spills either. And if you are inlining, then SROA and mem2reg will easily remove those load/store instructions. Modern compiler optimizations make what you describe not a problem anymore.
Yes, it should get inlined, but I've seen this fail in a recent-ish GCC (4.6 to 4.8 or so). And there's still the issue of debug builds being slower even if the compiler works perfectly in optimized builds.
Operator overloading will only work with SIMD if you write your vector class using intrinsics or SIMD extensions anyway. If you're doing scalar loads and stores, you can't rely on getting SIMD instructions in the output.
Seems like a pretty bad GCC bug then, one that should be fixed upstream.
I really dislike it when code avoids functions because of fear that they won't be inlined (or to try to work around compiler bugs to that effect), because doing this dramatically reduces code maintainability and safety in exchange for very little benefit, given the inline hint keyword and __attribute__((always_inline)).
I generally use always_inline for vector arithmetic functions, just to be sure. You never want to have a function call to do just a few SIMD instructions.
2.) complaints about debugging are invalid, sorry. we have amazing tools available these days and there's no excuse to suck at debugging.
3.) compilation is slow, he's right about that. #pragma hdrstop can help a bit there
4.) name mangling is a bitch, you can get around it by defining your exports, etc.
5.) everything else is just excuses for bad C++ code. it doesn't matter what the latest C++ style encourages you to do, what matters is writing code that works for your application.
I actually think it's awesome he's going the C route, it's just that he could probably do his project in C++ two times over in the same amount of time and I think most of his reasoning for choosing C is crap.
Didn't mean to come off like a dick so much, I wish him well, and I gotta admit alloca() is awesome
I make games in Unity/C#, and yes, of course it's slower than a custom C solution. But instead of spending time on writing my own containers and memory allocation, I spend time writing game logic. Instead of optimizing game to run from 100 to 150 FPS on my computer, I'd rather spend this productive time making 5 variants of the same game mechanic to find out which is more fun. Even if the game runs in just 30 FPS.
Of course, I sometimes try to research low-level stuff, write my own renderer, or do a small experimental project in pure C, but then my goal is to research, not to write a game. I can hardly imagine a project, where the goal would be to develop a game, and not to research, where using C and having to spend so much time on this matters would be a good trade-off compared to working on actual game mechanics in higher-level language.
The author states at the beginning that his goal is writing an engine and only after that write a game. You are right on all other points though, most people working on game engines simply never get to the making a game part. There's simply too much to do.
I spent a couple years' working together with a friend every now and then on a game+game engine in C#. We did get past the game engine phase because it was kept simple (ECS + box2d and simple monogame 3d renderer) but we eventually gave up because even though game logic was progressing swiftly assets were just a pain in the ass.
Most low level engineers (as we are/were) underestimate the work it requires to get a proper asset pipeline set up. You're never going to build a 3d game if you can't load cheap 3rd party assets with their animations and materials. And then the level designer which is basically just another game in your game.
We went with UE4 as a change of pace and man is it librating not to have to worry about the limitations of your homebrew engine anymore. In UE4 literally anything is possible (you have the full source after all) and its always less work.
I mainly use Unity now.
I've written several game engines in the past. I hope to never do it again.
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.
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.
This "class of games" includes more or less everything that a single developer without a very costly art department can produce. If your game can not be implemented with a prebuilt engine, it's really something extraordinary.
That said, I agree with your previous comment about performance. Rarely, if ever, should using one programming language over another limit the performance of any game so much that it would severely affect what you can and can not do gameplay- or feature wise. Unless you use some highly dynamic scripting language that fundamentally doesn't fit the problem domain, of course (I wouldn't write a 3D engine in Python or Ruby, for example).
I can somewhat understand some of the other arguments of choosing C over C++ (primarily build times), but for the most part the problems the author has with C++ appear to be philosophical. You can have almost everything you want in C++, if you're prepared to use it differently depending on your requirements. In terms of performance, it really is true that optimization only pays off for small sections of your code, so it doesn't make any sense to switch languages for that. You can most definitely make an extremely efficient data-oriented entity-component system in C++ without going having to jump through any hoops, for example.
I'm sure everything from minecraft and Dwarf Fortress through Battlefield 4 could have been implemented in a prebuilt engine.
Code-generation all the way. Use an expressive language like python/lua/tcl/lisp/scheme to work on a higher layer than C.
Two-language programming (one GC scripting, the other C) beats the heck out of C++, in terms of best of both worlds: expressivity in higher layer, performance in lower layer.
C++ has a dirty secret no one likes to talk about. Stroustrup himself was a two-language programmer. C++ was C with classes where the classes were built with unhygienic C macros. When he decided to show his work to "average joe programmers" of the world, he turned it into one language (it turned out he was not a very good language designer so the world has to live with it).
If you're working with C++ you are Stroustrup's average-joe customer.
If you're working with two-language programming you're Stroustrup himself (even better cz you're using a higher language way better than the C macro system)!
Other than that. You want superpowers? give your text-editor your C parser (something similar to this [1]). Structured-editing can do amazing productivity gains in C. This is something I'm still looking into (using some vi/vi-clone, or emacs/emacs-clone, and pycparser) but I very excited about the possibilities.
There are real costs to building the abstractions needed in order to make a useful scripting environment.
Tried to visit that site, but all I get is this message:
"Sorry, your browser is not fully supported.
There may be some issues with pages layout in your current browser. Please use an alternate browser until we resolve the issues. Thank you."
Since when is it OK to just refuse to serve a page if it's not 'the right' browser? Is this a thing now?
The opposite of what you say is easy to argue for: two languages, twice the headaches.
* first of all, you need to know TWO different languages. C is completely different from a language like Ruby or Lisp.
* even if you manage to do that, you still have two build systems, two things to package and deploy, you need two libraries of everything (e.g: unit testing)
* you need to constantly pass information between the two worlds, which can be both performance costly and challenging from a design perspective.
* normally it's not as easy as "rewriting the slow parts in C". What if your problem is overall memory usage? What if there is no one function to rewrite?
* if you make a mistake in C (super easy), your whole app crashes.
I don't think this is the magical solution you're presenting it to be at all.
- Unix philosophy and the shell utilities (bash + C)
- Emacs (lisp + C)
- Python scientific stack (Python + C/FORTRAN) (Heck even Google had to offer tensorflow in two interfaces, Python and C++. If they were going to offer a Python interface anyway, they could make their life 100x easier by implementing the backend in C instead of C++).
- git (bash + C)
As for 2 languages, twice the headaches, not if your 1 language is a multi-paradigm monster called C++. I'm not really sure if this used to be the mainstream advice when C++ was created but these days it's common knowledge that you should learn at least 3 to 5 different languages in order to improve your programming languages. If you can learn 3 to 5 languages, you can definitely work in 2 languages for your projects.
And by 'average joe' I don't mean any disrespect to C++ programmers. (I'm just commenting on the thinking behind the creation of C++). Quite the contrary, I have no doubt that it takes a lot more skills and hard work to program in C++ idiomatically than in C. Unfortunately, IMO, a lot of that skill and hard work is spent managing with the "accidental complexity" of the language, and not the "inherent complexity" of the project.
> 6. start to ponder if I really need even the good parts of C++
This reads like wisdom and maturity to me; unfortunate, but not surprising, that people are quick to judge.
Last game studio I worked at, we wouldn't have given up C++, but there were frequent conversations about its pitfalls and complexity, and quite a few rules and conventions recommending against, out outright prohibiting some C++ practices, exceptions, for example.
Before that, the last film studio I worked at, a bad experience with C++ led them to chuck it (before I got there) and go object oriented C. I learned their style of C and quite liked it. I missed operators and templates a bit, and it felt a little verbose, but I came to really appreciate the simplicity and explicitness.
Sounds like it wasn't an easy choice, but on a solo project that large you have to prioritize what makes you feel the most productive in the long term, and there are always tradeoffs.
I don't know the specific reasons std lib was mentioned, that's a big topic. But generally in the context of a game engine, you need all custom memory management and I/O, so there isn't much place for std lib, depending on which std lib pieces and which platforms and which C++ you're talking about.
- Introduce a lot of exit points which are hard to find
- They deviate from the "pay what you use," since they generate some extra code
- They can introduce some nasty performance penalties (I experienced this myself a few yeas back, the compilers might be smarter these days, maybe)
About the stdlib: There's a "myth" that often times the stdlib/stl is slow and doesn't do what you expect (unless you've thoroughly read the documentation, which everyone should do anyway). I can't say I was personally affected by this since I also use C++ for nothing too serious. I said "myth" because the argument against the slowness it that this is an outdated notion.
This can make C++ with exceptions faster than C with error checks because there's no branches to check for every error condition. Using __builtin_expect for marking the error conditions as unlikely may mitigate this issue.
Exception safe code is both difficult to write correctly and often has a significant run time cost.
But yes: exception safety, even basic safety (don't leak resources or crash) is difficult, let alone strong exception safety (no side effects if exception occurs).
Also, I observed that "bad experience with C++" is often related to someone using, while not completely understood, some more advanced C++ paradigm.
So there's a need to subset C++. I wonder. If we took the union of the most common subsets, would we have all of C++, or only parts of it? Which parts of C++ are unsuitable for any project?
If there is any, it is totally the fault of C++ if we have to subset it. Historical reasons yada yada, I don't care: a language you have to subset is still worse than the subset itself.
Could it be more that C++ is a decent language that is fashionable to hate on?
As for C++… That language is just too big for its own good. It tries to be too many things to too many people, and as a result is almost never the best choice. (Discounting code already written and available expertise of course. I'm judging the language, not the ecosystem.)
The idea of C++ is a good one, under a couple conditions: first, it must be tuned to a domain or a way of programming. Second, it must not try to be goddamn source compatible with C. If we're writing a language, we might as well correct C's mistakes along the way.
I feel if the author refocused on his C++ basics instead of bemoaning lots of peripheral crap (like OOP, and the unsuitability of the STL for game dev) he'd reconsider some joy in C++ again.
Maybe, I don't know. I have been a lot happier with C though, while still writing some C++ code during the year.
I think the main trick with C++ is to learn the best practices first, and only use those one thoroughly understood.
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
For example, the paragraph about loading and saving game state postulates "using the ideas of polymorphism and encapsulation", automatically throwing a lot of pointers and vtables in the way of reading and writing a binary blob like in the C engine. 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.
Some don't mind those, and be happy.
I agree with the OP on all three counts. I don't think RAII is very nice (python-esque with-statement would be nicer IMO), exception safety is really a mental overhead unless you restrict exceptions to a minimum and the copy-assign-move semantics do add a bit of work to every class you introduce.
These are not outrageous claims, they're rather valid opinions. Feel free to disagree but I'm siding with OP on this one.
Overall I dislike C++'s value based semantics, not because it's inherently bad, but it's just so different to any other (reference based) languages that only a small minority of programmers know how to work with them. Attaching complex semantics to types (overloading, templates) and virtual functions/inheritance (when overused) makes reading code much harder and often you need to resort to stepping in the debugger to find out where a function call actually leads.
Well written C++ can be really nice at best, but unfortunately most C++ code bases out there seem to be either a bastard mix of C, Java and C++ styles or over-the-board boost-ey template mess. Neither of these extremes hits the sweet spot.
You can implement Pythons 'with' statement in C++ with almost the same syntax and exactly the same terseness. This shouldn't be surprising, since C++'s value-semantic constructor-destructor mechanism is simply more general and fundamental.
> copy-assign-move semantics do add a bit of work to every class you introduce.
Only if you fill your objects with multiple dumb C pointers to other objects ad nauseam. If you embrace value semantics and smart pointers you almost never have to write any of the "rule of 5", and when you do it's straightforward. Simple rule: a class should never contain handles or pointers to more than a single resource. Composition handles the rest.
> Simple rule: a class should never contain handles or pointers to more than a single resource. Composition handles the rest.
The issue with this is that in the real world, you have to deal with 3rd party libraries and frameworks or even the OS syscall interface which don't follow this guideline. So you'll end up writing wrappers for all your "foreign" objects from any other libraries you use. This is a lot of work and creates impedance mismatch problems when there's no simple clearly established concept of ownership (e.g. the wrapped objects are already reference counted) or the assumptions of the library aren't friendly to C++ style semantics.
If you live in a "modern C++ only" bubble, this isn't an issue.
https://gist.github.com/nlyan/7319c7b2a6328460ce9c
As for external libraries...sometimes it requires a bit of ingenuity to find that perfect 'wrap everything!' vs 'write C' balance... but it's usually not so bad if you approach the problem judiciously. unique_ptr and shared_ptr's capabilities are often underestimated in this regard for instance. Both can be used with external reference counting, the former being the most efficient.
Excellent reference regarding Nim's GC and what it does differently here: http://nim-lang.org/docs/gc.html
Huh? Sure, you can go OO architecture astronaut in C++, but if that isn't a good model for your problem, don't do it. I see more of a focus on generic programming, anymore, in C++.
I've been thinking for a while that the problem with C++ is that 100 decisions have been made both in the library & language and in "best practice" that all individually are good, but the combined effect has been unfortunate.
I'm not quite getting from the article why you can't write in the C "subset" of C++ and add in a few of the more helpful parts of C++ though.
I think this is because of C++ name mangling, which prevents the creation of a quick and dirty reflection system. And maybe compilation times, which must be higher since the compiler has to deal with all of C++, even if you don't.
- user-provided code called when an "object" goes out of scope
- operator overloading sometimes makes sense
- simple template-meta-programming sometimes makes sense (but could be replaced with other sorts of code-generation)
3rd party libraries are usually a pain to integrate when they have a C++ interface, and usually simple when they are just a C header, and when they can simply be dropped in as source into the project.
A good middle-ground is probably to build the building blocks in C, this way they are way better reusable then writing C++ code, also across languages, and tie the low-level buildings blocks together in whatever language one likes (even interpreted languages).
[edit: formatting]
In this day of mobile and web gaming, indie developers everywhere, and game engines like Unity and UE4 being so accessible, I think more game developers are writing games in Java, JavaScript, C#, and other garbage collected languages -- not to mention "visual programming" of Unity and UE4.
If you're writing a game engine in C/C++ today you're probably never going beyond the desktop Windows platform.
However, the real reason Java and C# apps are most often much slower than native apps is because idiomatic C# and Java is a lot more work to execute. If you want to write really high-performance Java or C# code, you have to make it allocation free on hot paths, you have to be very careful with what types are on the stack and which are on the heap, and you have to pay a lot of attention to data layout (In java that doesn't have structs especially, making lots and lots of types as SoA instead of collection of objects for example).
The bottom line is: while you can make C# and Java code run really fast, it's very hard and non-idiomatic C# and Java code you end up with. It's so hard, that it's actually no easier than writing e.g. Rust or C++ code to do the same thing! And that is exactly the point where C# and Java no longer makes sense: if you can't use the idiomatic and ergonomic way of writing it, it has lost its value.
First of all, they are both great languages, no matter what anyone tries to tell you.
I went through this same dilemma and, while I have great respect for C, I soon realized that my situation would be more productive with C++ and its tools immediately available. For one thing, a lot of important libraries that are common in games and graphics have C++ interfaces, so if you use C you are asking of yourself to exclude some great tools out there, or, go through significant steps to wrap some of them (which would still involve some C++ anyway).
Don't focus too much on technology, disconnected from what people (including you) might want to actually use. Otherwise you will not get the real inspirations -- even purely technical inspirations -- that come from something that you intend to use.
The next closest thing is probably Swift alongside Apple’s FOSS reimplementation of Foundation for Swift, though I don’t know how well Swift works for development with OpenGL and such.
Modern Smalltalk-style languages can be fast. Look at Javascript! And in Obj-C, you can drop down to plain C when you need the speed.
It's possible to get your types mixed up when using non-generic collections, and that will throw an exception at runtime. No different from Go or Python.
In terms of memory safety, it's a step up from C if you use ARC, as all your object lifetimes are managed automatically. You can still get memory corruption if you're careless with arrays, but it's not really an issue with objects.
Possibly this is just Stockholm syndrome, but the more I use Obj-C, the more I like it. It uses reference counting, so memory overhead is low and consistent, and running time is consistent. It's not fast, true, but in my case most of the running time is spent in OpenGL anyway. Plain C is always there at your fingertips for crucial inner loops. Newer features like properties and for-each loops make it fairly pleasant to use. The standard Foundation library is pretty well-designed.
The one thing it's possibly missing is generics (Apple added that feature recently but I haven't tried it yet). Without generics, it feels a bit like a reference-counted Go -- that slight scripting language feel but with native speed.
The one big downside... On non-Apple platforms, the tooling is very patchy, and new features can take a while to arrive. If Apple is really serious about open-sourcing Swift, that could help a lot. I imagine there'll be more interest in new shiny Swift than crufty old Obj-C, but Obj-C is still a nice little language.
[Edit: capitalization of GNUstep :)]
That said, author has done a good job explaining his thought process in taking the rewarding-at-every-step path, given he's not a C++-wrangling junkie.
It is still changing fast but I think it could be great for writing games in the future.
Depends on what you consider safety to be. A game engine that doesn't deadlock and doesn't crash should definitely be high priority!
I don't really agree with this assessment (I think rust's "safety guarantees" really amount to amazing compile time programmer assistance), but that's their stated reason.
Exploiting buffer overflows, stack corruptions and friends is how we get to earn extra lifes, bypass hard levels, get extra ammunition and so on.
Someone has to.
> New and hip GC'd languages don't care about the priorities efficient game engine code has. Safety-enforcing languages like Rust are going somewhat off already by definition, as they're focusing primarily on safety, which is not the focus for most game code.
However, I disagree with this. Rust does enforce safety, but once you have a feel for how the borrow checker works (which isn't hard at all coming from C or C++), it fades into the background. The real benefits of Rust for games are the build system, the module system, affine/linear types, and all the various syntactic features like pattern matching and expression-based control structures that make it way less crufty to read and write than C or C++.
Rust's biggest weakness for game dev (again) isn't the enforced safety, but the still-growing ecosystem. There's a few really good low-level libraries (Glium, for example), but if you stray beyond them it turns into a nightmare of writing your own FFI definitions and unsafe glue code.
It's ridiculous how much work these engines will save you, once you know how to use them, but ecosystem is key because these engines are HUGE.