Hacker News new | ask | show | jobs
by teknico 3287 days ago
It's a lost cause: there's no such thing as "sane" or "orthodox" C++. Instead, use something actually sane like Go. Or Rust, if you really must (first bad pun is free, then it's 50 cents each).
7 comments

This comment is so asinine, it's depressing it's at the top of the comment section right now. Why can't you just be nice?

Anyways, C++ is much easier to manage than any GC language for a real time application such as this. And your Rust "pun" is a rhyme.

Fabien Sanglard and John Carmack definitely disagree with you on this regard:

http://fabiensanglard.net/doom3/index.php http://fabiensanglard.net/doom3/interviews.php#qc++

Go is a bad choice for game with its unavoidable GC. And Rust is simply too complicated for a person wanting sane C++.

D or Nim (or even C!) would make more sense.

Is there not a point where GC overhead becomes negligible? There have been production examples of Go maintaining sub-millisecond GC pauses with a multi-GB heap under a server workload. https://twitter.com/brianhatfield/status/804355831080751104

Surely there are better reasons to disregard Go for gamedev by now.

I've dipped my toes into using OpenGL and Go a few times and it seems pretty nice. But I'm entry level graphics programming and so it scares me off to go further (surely someone would have used Go by now in a 3d game, if you can do Minecraft on the JVM, why not Go)?

I keep wondering if it's not possible or if there's just not a lot of overlap between Go programmers and gamedevs? Wish someone knew.

I wouldn't predict a lot of overlap. On the game dev side, Go offers nothing new, and the forced untuneable gc will be an instant turnoff for the usual reasons. On the Go side, my outside perspective is most of its users seem to be focused on web server backends, trying to justify async everywhere, and/or rewriting slow Python or Perl or Bash scripts and assuming that makes it a systems language.

Go users interested in diving in to SDL or OpenGL bindings to make a game shouldn't be discouraged. Lots of games are made in all sorts of high level languages, the heavy lifting is put onto a few native libraries. But if the goal is to make a general engine, I'd question its utility apart from fun/learning. Again there are game engines in high level languages (with dark native-level secrets in any that try to be performant) but they don't seem to get traction. Even an engine in e.g. C++ doesn't necessarily help your performance goals (http://www.yosoygames.com.ar/wp/2013/11/on-mike-actons-revie...) if your plan is to make it general instead of make it just support whatever sorts of games you're making and planning to make.

The vast majority of memory in a modern game stores data for the GPU. On a game console the game manages this data itself. If you ever want to ship on a console you better be sure Go can handle the memory visible from the GPU correctly e.g. keep the necessary alignment, ensure the GPU is not reading/writing the memory it's going to modify, do not charge address of anything, preserve layout of structures etc. I never used Go so I don't know if it already does this. But I would be considering these things way before I'd even started looking into performance.
> Go is a bad choice for game with its unavoidable GC.

Better let the Unreal guys know about it then.

There's a big difference in engine support for GC of a certain object type with lots of support for tuning (https://wiki.unrealengine.com/Garbage_Collection_%26_Dynamic... and https://docs.unrealengine.com/latest/INT/Programming/UnrealA...) and language-level untuneable GC of everything.
Not all language-level GCs are untuneable .

Also if one doesn't allocate like crazy on the heap, there is no reason the GC needs to work.

I agree, but the GP comment was in the context of Go. Nim is a newer option I'd like to see tried more, its GC is optional and swappable.

If you never run out of memory you'll also never need to GC. ;) Or even free(), just let the program finish and reset the machine. (Actually not too weird in some embedded systems...) Some languages make it easier to not heap allocate than others, or notice when you are heap allocating. I hear Go does better than Java in this regard. But if you're facing a performance issue at the level where you're fighting the GC as the biggest barrier, and the language doesn't give you much assistance (like being able to choose latency/throughput tradeoffs or controls on non-determinism), that's a sign the language isn't that suitable for that performance problem domain. With performance sensitive games, you're already in the corner of having to worry about hardware details, so there's a strong incentive to just start the fight at the beginning without your hands tied by some language's static GC.

Not all games need to be the next Crysis.

If fact, the majority of them gets abandoned even before memory pressure starts to be a relevant issue.

Even if Go isn't at the same level of D or Modula-3 in regards to memory management (heap, stack, global), it is already quite usable for many types of games.

The important difference is that not everything in Unreal is GC'd, only UObjects. Internally the rendering, animation, networking and other high-performance subsystems don't use GC because they can't afford the overhead. Being able to opt in to GC where it's useful is great, being forced to use it everywhere can be a hassle.
> Being able to opt in to GC where it's useful is great, being forced to use it everywhere can be a hassle.

True, but just because a programming has language level GC, it doesn't mean it must be used everywhere.

If one doesn't allocate like crazy on the heap, there is no reason the GC needs to work.

Also using value types is always an option.

One also doesn't call malloc() in such high-performance subsystems.

If you're gonna bother with orthodox C++, or Go (suited to 3d games? nobody seems to have tried or lived to tell the tale), or Rust (no libraries anyway, so you may as well use something which wraps C easily and is higher level), why not try Nim?
I think if you were writing C, then Nim might be a good alternative. If you're writing C++, then Nim offers a lot of similar metaprogramming features; but it lacks RAII, which IMO is a monumental drawback. People may dislike C++ for many reasons, but RAII is a killer feature -- there's no question why languages like D and Rust adopted it.
We're slowly getting there though. I hope to release a blog post soon about how it might look like in Nim.
IMO, Go is not sane.

Rust, however is great.

sane like Go

Yet Go uses the oh-so-error-prone return value error checking that has been so successful in C :/

Disclaimer: Not a go apologist, I admire it, but don't use it.

With Go you get a compiler error if you don't do something with that error. You have to explicitly decide to ignore it with `_`. As far as I remember that's quite different from C where you can get an error code, ignore it, and never realize you've missed it.

Not quite. Try this:

    import "os"

    func main() {
	    os.Open("this file does not exist")
    }
This will compile just fine, producing no compiler error or warnings whatsoever. The error is just silently ignored.

Compare to Rust:

    use std::fs::File;

    fn main() {
        File::open("this file does not exist");
    }
This will produce the following warning:

    warning: unused result which must be used
      --> test.rs:14:5
       |
    14 |     File::open("this file does not exist");
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: #[warn(unused_must_use)] on by default
This example is a bit contrived, because if you open a file you probably want to do something with it. But imagine something where the only return value you care about is the error, like say "txn.commit()"
A slightly related note: you can get similar behavior in C (and C++) with compiler extensions. In GCC and clang marking function with '__attribute__((warn_unused_result))' will produce a warning if function is called without using the result. Equivalent for MSVC is '_Check_return_'.

Obviously this is not nearly as convenient as your Rust example, but enables some of its the benefits.

C++17 has (will have?) [[nodiscard]] with the same semantics.
Still better than

try:

  ...
except:

  pass
I wouldn't say so. This screams "I am an idiot", missing handling can easily pass unnoticed.
Hah! It could be worse... imagine your example, but in Java with checked exceptions ;)