Ask HN: If C++ is so bad, what should game developers use?
I've been reading some of the C++ hate that's been on HN of late. I've been successfully convinced (not that it was hard) that C++ is a really terrible language. But, if that's the case, then what else are commercial game developers to use? Is pure C sufficient? What about Haskell, or is that too slow?
126 comments
[ 57.5 ms ] story [ 594 ms ] threadOne route out of this local maximum could be CPUs optimized for garbage collection and a high rate of function call. I doubt we will see that any-time soon, though.
Computer games are another story, but the same facts are generally true, that you should use the language which provides the libraries where most of the work is already done for you. There are the most number of game development kits for C++, so that's why C++ is popular for game development.
For what it's worth, I have done a few games using the ORGE rendering engine and plugged the game elements (physics, AI) with separate libraries and it has worked fairly well.
http://en.wikipedia.org/wiki/OGRE
http://en.wikipedia.org/wiki/Havok_(software)
http://en.wikipedia.org/wiki/Box_2D
Unless you want to write a lisp using that c/c++ sdk and then develop with the lisp you wrote. (http://en.wikipedia.org/wiki/Game_Oriented_Assembly_Lisp). So it certainly can be done. Whether you should is another matter.
Post acquisition (by Sony) Naughty Dog seems to have shifted to c++ but seems to have shifted back partially to lisp (C++/ scheme combo as far as I can make out) for their "Uncharted" games for the PS3.(http://www.naughtydog.com/docs/Naughty-Dog-GDC08-Adventures-... Warning PDF)
The point doesn't really stand, no. A big studio making a game over a long time period will obviously do a much better job than an undergraduate writing his thesis (yes, Frag was an undergrad thesis project).
To be fair, I think there are legitimate difficulties making games in Haskell, primarily related to the predictability of performance. GHC is a complicated and rather ingenious compiler incorporating many optimizations. While this is useful, it also means that performance (especially the responsiveness of the GC) can be unpredictable. Predictability of performance is quite important for games. In fact, this could be a good reason to try writing games in Ocaml.
The more probable reason is simply that the set of people working on big-budget games with the expertise to do in Haskell what they've learned to do in C++ is small.
Haskell has OpenGL bindings: http://www.haskell.org/haskellwiki/Opengl
So does OCaml: http://glcaml.sourceforge.net/
After that, if necessary, they can look at lower-level details (escaping to a high-performance library in another language, skipping things like array bounds check, other low-level tricks, etc.). However, one of the powerful advantages of Haskell is that this sort of low-level, error-prone, ugly code can be isolated behind clean, predictable, functional interfaces. A great example of this is Haskell's ByteString library, where the low-level details are complex enough that I don't necessarily understand all of them, but the external interface is one even a beginning Haskell programmer can effectively and efficiently use.
Are there other 3D/multimedia frameworks that play better w/Haskell? Or is it just raw OpenGL?
Perhaps the reason there are no other viable options is because C++ is pretty well suited to the task?
And for certain tasks, like the highly competitive video game industry, uncompromising performance is exactly what the doctor ordered. You can always hire smarter programmers if you have to. On the other hand, it's hard to sell a game that runs or looks like crap at 60 fps.
The complexity is redundant because they included features of C that they should have deprecated in favor of the C++ equivalents. String literals should be (const) basic_strings, and there should be no way to access stdio's horribly-easy-to-buffer-overflow machinations now that there's iostream. For that matter, boost should be adopted at a much more formal level: the default syntax for pointer variable allocation should allocate smart pointers, and one should have to go out of their way to get a dumb one. None of these things sacrifices performance; they're just artefacts of the fact that C++ wants to pretend it's still 1970 and that it's being used for systems programming in a mélange with C.
It's confusing because there are two types of type-inference (run-time and compile-time). You do lose performance with run-time type-inference, which is what most interpreted languages use. C++ already has compile-time type-inference in the form of templates. They are...complex. But they're also faster than anything similar. There's a reason that C++'s sort is usually faster than C's qsort for complex types.
C++ doesn't have run-time type-inference and probably won't. The performance loss is too big to build it into the language everywhere, and unless you do that it's pretty pointless. You can tack it on with boost::any or RTTI, for example, but you'll generally find that it's not the correct decision from an engineering standpoint. Scripting languages have run-time type-inference built in everywhere -- but they do it at the cost of performance.
Now, as far as the problems of C++'s dependence on C...well, you're right. It's a feature of C++'s history. If someone could come along and invest the millions of man hours required in making a performance-critical high-level language without the C-baggage (and then market it!), it would be a great thing. But that's a pipe dream. The best we're going to do on that front is Java.
The pipe dream you are talking about has a tiny hope of coming true here: http://www.bitc-lang.org/
EDIT: replying to thras, below.
According to the very Wikipedia article you cited, there is no such thing as "runtime type inference". What you described as such is a way of implementing dynamic type checking: checking at runtime that the types of the various parts of an expression actually match. You should actually read your sources.
So, "type checking" isn't always performed at compile time.
Type checking is a little different, and refers to some safety measures done at compile time that theoretically guard against certain types of errors. Scripting languages don't do them, and don't miss out on much programming correctness as far as I can tell.
Full run time type inference in C++ is about having a container of something -- let's say void pointers that just point to memory locations, and figuring out what sort of thing they're pointing at. Or similarly, you have a base-class pointer and want to figure out which derived version of the object is being pointed at.
Note that this term isn't very widely used: we are already in second position at your link.
So, unlike ML-style compile-time type inference, runtime type inference is implementation specific. Your earlier statement "Scripting languages have run-time type-inference built in everywhere" is actually ill-typed. However, if you had said "checking" instead of "inference", your sentence would have been correct.
Hence my "Err, by "runtime type inference", you actually mean runtime type checking, right?"
"Runtime type checking" is the same thing at runtime. Errors and exceptions get thrown, your program stops. So no, that's not what we're talking about.
Here is a description of type checking: http://en.wikipedia.org/wiki/Type_system#Type_checking
I can't seem to find that statement anywhere in the article. And it hasn't been edited since September. Also, why not google for "runtime type inference": http://www.google.com/search?hl=en&client=firefox-a&...
You said "scripting languages have run-time type inference built in everywhere". I don't think that's true for most scripting languages. Inference refers to determining facts which were not explicitly provided, and usually implies a statically typed programming language where inference is done at compilation time. "Runtime type inference" might be used for, e.g., Python's Psyco project, where the just-in-time compiler infers properties about a particular variable (such that it's always an integer) and can therefore compile out boxing, unboxing, runtime type checks and so on.
Speaking of runtime type-checking, I inferred that that's what you meant when you said "runtime type inference". Type-checking IS something that happens at runtime almost everywhere in dynamic languages. It is also more closely associated with RTTI -- a dynamic_cast in C++ would not be termed type inference (the previous and new types are known), but a type check does occur at runtime.
As long as it also had C syntax, and something close to the C memory model, and was recognizably object oriented and/or functional. And came with a lot of libraries. And 3D graphics engines. And physics engines.
Unless some independent game developer does something in a new language that other developers can't easily duplicate with their current ecosystem (not that it couldn't be done in C++), and it catches on, there's no incentive to do anything other than continue to evolve things in the most backwards compatible way. There are always more C++ programmers coming off the assembly lines who want nothing more than to work on games.
There is such a language, it is called C.
The thing is, it doesn't matter. The fact that it's the leading language in your domain is much more important than its abstract quality. Availability of libraries, co-workers who understand it, examples of how to solve common domain problems in the language, these all outweigh the pitfalls and ugliness of the syntax.
Don't be seduced by the language wars. It's like spending days optimising a function that only takes 10% of your run time. Unless you're interested in the learning exercise (which I'm not knocking if you have time) pick one of the dominant languages in your domain, and spend your time worrying about everything else.
You can justify using any language by saying that. Such a vague statement.
That's kind of the point, isn't it? I use the languages that I am most familiar with, because I am far more effective with them than I am with whatever this year's new awesome language is.
If I had been doing Ruby from the beginning, then I should continue doing Ruby, and not use C++ just because someone else says so. Likewise, if I've been doing C++ for as long, it doesn't make sense for me to switch to Ruby just because someone else doesn't like C++.
This stuff is so much nonsense. I think I will start using programming language evangelism as a reliable marker of a less experienced programmer.
Using your example languages. I can think of situations where using C++ rather than Ruby would be idiotic, and likewise situations where the reverse is true.
Because, from what I've seen, a lot of people can be very excellent programmers in one language, but be unable to learn to program in another.
Actually, I don't think it's really "less experienced programmers". Several of my computer science peers (I'm 23) -- inexperienced as professionals, but very smart -- will try the same problem in four or five languages, just to learn the language, and have no fears about jumping into a new language to start a new project. On the other hand, I know some excellent older programmers (late 40s) who know C++ super well, are willing to apply jaw-droppingly complex template metaprogramming, but who only reluctantly use Python and won't touch Ruby or Scala. It's probably more of a cultural thing.
Inexperience doesn't mean a person isn't smart, or that they aren't good or that they aren't clever. All it means is that they haven't developed a significant body of familiarity with something.
So, your peers are learning several languages -- maybe superficially, maybe not -- but they aren't yet developing the familiarity with one or a couple of languages that allows them to feel comfortable mentally solving any given problem in the language they're most familiar with. Thus, you have "Ruby" problems, "Python" problems, "C++" problems, and so on.
Those older'n-dirt programmers on the other hand have already solved a huge number of problems in C++. That doesn't mean they're a better programmer, but it does mean they're more experienced. So, if you ask them if they could write X or Y or Z, they'll say sure -- and in their head, they're probably already gathering the familiar pieces that they would need to solve it.
Or, to put it another way: if you went to pg and asked him to write software to run a forum like this, he would probably choose to do it in Lisp, and that would be a good choice for him. If you asked me, I would do it in PHP. If you asked DHH, he would do it in Ruby (on Rails).
None of those are wrong.
The only wrong choices would be me coding in Lisp.
That would be horrifying.
Modern game developers should code in Ruby/Lisp. By the time you've finished coding all of your graphics libraries from scratch, Moore's law will have made computers fast enough to run them.
If you think that Ruby/Lisp is your answer to the above points, go for it. But I have the feeling that it won't be for many people.
Besides, isn't the advice for optimizing every other kind of project:
1. Start in a high-level language
2. Port any bits that profile as slow to a low-level language, and then interface them into your HLL code
Why, all of the sudden, when you're coding a game, is it a better idea to start in the low-level language? Because your team "knows it?" by that argument, all games would still be being developed in assembler, because the most senior members of the team would have more experience with that than any new-fangled language like C++.
The only way you might get away with this approach is if your application is essentially a number of separate scripts that communicate through files. Then you can rewrite the key scripts in C++. But that is never the case for games.
That is a pretty common approach, but what was suggested above (writing first in HLL and then rewriting parts in C++) is, to my knowledge, never done in practice.
"There is not a simple set of “hotspots” to optimize!" and also "Will gladly sacrifice 10% of our performance for 10% higher productivity [...] We never use assembly language."
Besides the issues of programming in a language different from what your libraries were written in (and more importantly, designed for), there are serious issues with the patchwork-performance code philosophy. The biggest is that the "routines" that you need to make fast often operate on data. You'll have a container of some sort in your "slow" language, and you'll want to do something with that container in the "fast" language. Do you expose the container's memory to the fast language (hard!)? Do you copy (slow!)? Do you use the more efficient containers in your fast language and expose them to the slow language (insane!)? When exposing complex objects between languages, you'll have similar considerations to make.
You can't look at C++/Python or C++/Ruby programming and assume that you'll simply have to make the same sort of engineering considerations as you would for C/Assembly. It's an entirely different kettle of worms.
Assembly is concerned with telling a computer directly how to handle memory addresses. C, for all intents and purposes, is just a simple abstraction for doing that. On the other hand, higher level languages present a lot of abstractions for doing a lot of things. And mixing those abstractions leads to serious complexity.
As an embedded core to sort out a bunch of logic they definitely have their place.
If the library uses weird pointer tricks like "since every data item is 4-byte aligned, we can use the low two bits of the pointer for flags", you'l break the garbage collector. Again, not great practice, but it happens.
I love dynamic languages. Every so often I try to write a game in Lisp, but productivity is about Language + Runtime. You can only justify fixing broken GL wrappers for so long, before you realize you've wasted time that you didn't have to waste.
My current fav mix is C++ without aggressively using all the bells and whistles, so it's easy to wrap for dyn languages, and Python.
Very fine sarcasm, sir.
It used to be that almost all languages compiled into machine code.
And you, get off my lawn.
Since their basic approach is applicable to other platforms, it will be interesting to see if GHC ends up with clean, extensible support for mobile cross-compilation in the future.
Python is excellent if some good soul has already written a library that does exactly what you need (in C or C++, obviously). The combination of numpy+PyOpenGL+PyCUDA is great for certain kinds of research projects (I have written a volume renderer in it) but probably far from commercial usability.
Haskell has great potential, but the community is too small and too academic to produce the necessary libraries and tools (which is a lot of work with minimal scientific content). The key problem is the absence of an industrial-strength array facility - there are many kinds of arrays in Haskell, all somewhat clunky and incomplete.
D might be a contender, but last time I looked the tools seemed very basic and it did not even support 64-bit systems.
D is a dream to program in. The templates, especially, are so much nicer than C++ that I hate coding in C++ for a few days after doing D. OTOH, it's the same thing wrt runtime. Why spend a few days getting other people's crappy wrapper code working, FOR EACH LIBRARY, when you already know how to use, debug, and optimize those libraries for C/C++. It wastes a lot of time!
Don't buy into the nonsense. C++ is a fine language. People have been using it for decades to do real work, and they probably still will be in another two decades, long after Ruby and Haskell and Blub have been discarded as 'archaic' by the next generation of 20-year-old language snobs.
My use of other languages has therefore gradually increased - I currently use Clojure where I can, but I'm pretty flexible (PHP being the only language on my blacklist so far).
For projects on which I'm a lone programmer, I'm free to use whatever language I want in theory; sometimes the customer has a preference, usually not, so the main restrictions are technical and legal:
I'd like to use Clojure a lot more, but for game dev, it's problematic. Performance is the least of my worries, as I can drop to Java or even JNI for the rough bits. It's also a fantastic choice for server-side programming on multiplayer games. However, the JVM isn't allowed on the iPhone, say, and unrealistic or disallowed on consoles. (the Nintendo DS has 4MB of RAM, for example; licensing/porting is an issue) For pure PC/Mac game dev it's probably fine, although if by any chance you want to port later, good luck.
There was a submission on HN a few months back about using Gambit Scheme for iPhone and Mac programming. Googling easily retrieves some useful information on this, but the general idea is that it compiles to C, and you can actually write Scheme functions with inline C/C++/Objective-C code, so you're using a 2-stage compilation process and retain full control while using a very expressive high-level language. I'm going to try this with my next iPhone game, as I'm not all that impressed with Objective-C so far. It looks extremely promising.
There are of course other languages which have compilers that generate C; I believe there are some Common Lisp implementations, although especially for game dev I'm not sure if there's any advantage in using CL over Scheme.
If using a full-blown Lisp feels too high level, there are some Lispy efforts that are essentially very fancy C preprocessors, e.g. BitC[1] or SC[2]. The latter is literally C with an S-Expression syntax; I'd be interested how compactly all of C++'s features could be expressed in such a language. Not that you'd want the arcane contortions of C++ templates when you have real macros.
Moving away from C a bit more, there are of course Forth and other languages in a similar vein.
As I've mentioned, I'll be going down the Gambit Scheme route in future projects, as it has a very nice interface to C/C++ (this is critical when dealing with game development oriented libraries - I'd probably make this the top priority in choosing a language for this purpose), it's very stable and mature, and it's a Lisp. I'll try and write a postmortem of some sort when the time comes.
My main worry is the behaviour of GC in an environment with hard memory limits (no virtual memory or paging), but if the allocator and GC are well written it should be less risky than explicit memory management.
[1] http://www.bitc-lang.org/
[2] http://super.para.media.kyoto-u.ac.jp/~tasuku/sc/index-e.htm...
Damn you, pusher! I don't have time for hobbies! Uhm, I mean -- thanks for a really informative post. :-)
Moreover, I actually like C. But it is inconvenient. On the other hand C++ is convenient, but I don't like it. So I started playing with developing a dialect of C that adds support for parametrized code, closures, this pointer and type inference. All the stuff that I use or would like to use in C++, but in a syntactically cleaner way and without all the blubber that C++ accumulated through a design by committee. A hobby project, nothing too fancy :-)
I want it because it is an interesting project in itself. But I hear what you are saying.
See: http://www.c2.com/cgi/wiki?AlternateHardAndSoftLayers
ps: Lua, not LUA -- it's the Portuguese word for moon, not an acronym :)
http://libregamewiki.org/LOVE
"LÖVE is a cross-platform, 2D game engine. The latest version of the engine is 0.5.0 released on September 9th, 2008.[1] It uses the SDL library, OpenGL hardware acceleration and the Lua programming language to program games.[2] It is licensed under the Zlib license. "
Lua strikes me as a much nicer interface to C libraries.
If you're writing your own game project you can use a sane, minimalistic and syntactically pleasing subset of C++ and gain all the pedal-to-the-metal advantages. Don't discard C++ because other people say it is terrible. Other people are usually wrong. Make up your own mind.
For example, the Keyspace code is organized into classes; we use inheritance and polymorphism; we use templates in a few select cases; but we completely avoid exceptions, operator overloading (actually operators) and the STL altogether (we implement our own containers). It doesn't matter what others think about this specific subset - it works for us, we work well in this formalism, make few mistakes and produce good code that runs fast.
Pragmatically, for the OP, I think it's safe to say that C++ and Haskell are both very challenging languages that take a long time to master. The advantage of C++ is that there's ample evidence of its suitability for various domains. The advantage of Haskell is that you'll learn a different way of programming. The decisive factor may be whether it's your own money on the line.
i haven't used either, just conjecturing based on what i've heard through the grapevine.
I would write as much as possible in Python and use C for the performance critical stuff (which, for a game, is a large portion).
Eve online is a great example of a game for which almost all game logic is in python (stackless). The "engine" is in C++, I think (not sure on that one). So I would adopt their model but use C instead of C++.
http://www.python.org/about/quotes/ (look for "Firaxis Games")
http://love2d.org/
Haskell's extreme facility with small light threads and its implicit parallel "strategies" could also make updating a rule based game board an almost mathematical, rather than detail-grovelling exercise.
You will not be able to get monkeys to program in Haskell. Whether this is a problem depends on your plans.
Most of the performance of a game stems from finding a sweet spot combination of data structures and data relationships so that data is processed at real-time rates, is accessible everywhere, and can be immediately applicable to the algorithms you plan to use.
If you design relationships-first, the way you would design a database, you can figure out a data layout that doesn't involve any high-level semantics to speak of, just classic data structures from CS: lists, arrays, graphs and trees to order things, records and hashes to name them. Go through each type of value and cross-index it across all the structures it needs to be accessed by.
Then write intersectable queries into the structures to express a complex gameplay question like "Find all the enemy actors that are near mission objective X" as the composable "Find positions between boundaries A,B,C,D, that belong to actors of the enemy type, where A,B,C,D are some distance relative to the position of the objective entity with the name of X." Since you aren't writing a generic all-purpose database, this isn't a hard problem. It's just time-consuming to nail down the data model that captures all of it.
I think you will agree that a fancy language isn't necessary to implement such an approach to game programming. Done that way, data mostly ends up being pointers.
If your game is processing-light(which with today's desktop hardware primarily means avoiding 3d computation), you don't need a fast language: I'm most familiar with doing game code for Python and Flash and they can do just fine on modestly-sized datasets too.
from: http://www.economicexpert.com/a/Forth:programming:language.h... "Forth became very popular in the 1980s because it was well suited to the small microcomputers of that time: very efficient in its use of memory and easily implemented on a new machine. At least one home computer, the British Jupiter ACE, had Forth in its ROM-resident OS. The language is still used in many small computerized devices (called embedded systems) today for three main reasons: efficient memory use, shortened development time, and fast execution speed.
Forth is also infamous as being one of the first and simplest extensible languages. That is, programmers can easily extend the language with new commands appropriate to the primary programming problem in the particular application area. Unfortunately, extensibility also helps poor programmers to write incomprehensible code. The language never achieved wide commercial use, perhaps because it acquired a reputation as a "write-only" language after several companies had product failures caused when a crucial programmer left. In addition, the ease of implementing Forth on a given processor meant that the barrier to self-development of a Forth system was quite low, so that commercial suppliers were, in effect, competing head-to-head with hobbyists, many of whom supported the idea that software should be free."