1. 26
  1.  

  2. 18

    There’s something important that’s being missed here, due in no small part to the FUD of people who are brainwashed into thinking that C is hard. The problem space and culture is just very different than what a lot of these folks are used to.

    Working on a game is about as different from normal application development as you might ever get. You will not need to maintain it. You (if the survival rates of studios are any indication) will not need to support it into perpituity. You are (in the case of consoles) guaranteed certain hardware behaviors.

    You will spend a lot of time debugging and tweaking. You will spend a lot of time making lots of sweeping changes for whatever reason. You will spend a lot of time doing finicky little optimizations because the world is cruel and unjust. You may find yourself having to scale (on the PC) from machines that are better than most EC2 instances down to a measly Raspberry Pi.

    The thing that people don’t seem to get is that, in a more featureful language (cough C++ cough), the language starts to really fight you once you start making these changes. C++ has this elaborate template and class system, and it’s really great and wonderful if you don’t find yourself having to make big refactors. If you do, though, life gets annoying fast. Abstractions you thought were correct turn out rubbish, and can even (in the case of, say, writing a graphics pipeline) create permanent performance deficits because you are working in a thoughtspace totally divorced from the reality of hardware. And in games, guess what: the hardware matters.

    C code is also trivial to patch up without doing something utterly bonkers by somebody else on the team. It’s all right there what is going on, and as such it is super easy to follow the logic. C++, especially with function and operator overloading and multiple inheritance and weird tricks with references and whatnot, has no such cap on complexity.

    The thing is, I guess, that C is easy to maintain in the field and easy to add last-minute hacks to without things just blowing up everywhere. C++ or other languages can get really wonky as you pile on hacks, and can outright prevent them from happening if you follow their best practices.

    EDIT:

    Also, the boilerplate isn’t bad at all if you choose your abstractions and modules carefully. And pointers are not some scary eldritch trap waiting to byte you. :|

    1. 4

      A big part of this is choice of abstractions.

      C++ provides more expressive power than C, which in turn demands more from the developer: do I use this language feature to represent this? Do I know all ways in which it will be used, and what the perf implications are? Can it be used incorrectly? How does it interact with the architecture as a whole? What is the equivalent C code for this abstraction? C++ demands that you ask these questions about every abstraction you create. Problem is, C++ presents a big bag of tools, which tempts developers to use them all, and think about they all interact together. This is not a language to fart around in and “try things out.” You need to know what the right tool is for the situations you run into ahead of time. It won’t guide you to them! I think it gets a fairly bad rap from the Internet partially because of this.

      I like to say that C++ requires a lot of taste to use well, but I don’t want to make it sound like I’m excusing some of the choices it has made. You have to be critical about just about every abstraction you introduce in your program, verifying that it is what you want. You’re going to have a bad time if you don’t do that.

      I don’t use C++ much anymore (thank goodness), but I learned a lot from it:

      • absolute necessity of attention to detail when programming (indexing off-by-one error: possible memory corruption causing crash much later)
      • value of simplicity in solving problems
      • learning that production code is the worst place to expand
      • possibilities of generic programming

      One last point: C++ used to be hot shit in industry. Like, Javascript-level hot. Now look at how it’s regarded. Don’t think for a second that it won’t happen to JS, and whatever comes next. Today’s coolest, hottest thing ever is tomorrow’s despised, ignored language. Fundamental programming concepts, like abstraction, modularization, and solid knowledge of paradigms age really well. Language-level minutiae? Not so much.

      1. 5

        My small pet theory: When writing C, one writes a lot of boilerplate code, and this can be a soothing activity for developers. Its a bit like knitting on the couch.

        1. 2

          I felt tbis when writing go. Its kind of satisfying to just be typing for a while.

          1. 1

            This rings true to me. When I wrote a lot of C, there was this ladder from boilerplate up to almost-correct abstraction, usually through terrible pre-processor hi-jinx, which would then crash back down to lots and lots and lots and lots more boilerplate.

          2. 2

            Regarding the maintenance aspect; this was true a couple of console generations back and holds true for a subset of today’s games, but a larger and larger subset do require longer term maintenance.

            Since consoles have all gained online connectivity it has become a continuous cycle of 1) launch, 2) patch, 3) release expansion repeat steps 2&3 until the checks quit coming in.

            Popular multiplayer PC games follow this as well especially as there is a larger movement toward freemium. League of Legends was released more than 5 years ago and still releases updates roughly every two weeks.

            The majority of successful mobile games follow the freemium model of continuously updating.

            1. 1

              Freemium is a really terrible development and business practice for everyone, devs and players alike.

              I appreciate that it’s been proven to have efficacy in business, but it’s ruining the games industry.

          3. 5

            Lets contrast what he says about C++ and C:

            C++ covers my needs, but fails my wants badly. It is desperately complicated. Despite decent tooling it’s easy to create insidious bugs. It is also slow to compile compared to C. It is high performance, and it offers features that C doesn’t have; but features I don’t want, and at a great complexity cost.

            C is dangerous, but it is reliable. A very sharp knife that can cut fingers as well as veg, but so simple it’s not too hard to learn to use it carefully.

            Now C++ is mostly, but not strictly, a superset of C. The language can be fairly referred to as desperately complicated when taking the whole language into account. It is also slower to compile when using much of those features.

            But C’s lack of abstraction creates a large cost in lines of code to get some simple things done. For a simple example, if I want a list of items that can be added to and expanded, in C++ it is very easy to do so. In C, it is still possible, but you might have to write it yourself (by extension it won’t be used and tested as much as the solution in C++) and you have to use pointers to do it.

            Why not opt for something in between? Start with compiling C with C++ and add only a few features that improve things. For example, using classes for organization is certainly not the same as OOP, especially if you aren’t using any hierarchy.

            A great example of a feature from C++ that could be adopted without much complexity is the most simple template usage. This may be more applicable to reusable libraries, but if he is creating as many games as is implied, he would have a lot of reusable functionality. I am talking about throwing a template parameter onto a structure to make it more generic. Much more reliable than C’s void pointer cast. This is also the same thing that keeps me from using Go in any big way.

            1. 6

              Human factors are at play here. Writing reusable/generic code is an order of magnitude harder than writing one-off code. Copy/paste is a perfectly acceptable code-reuse strategy for games, given that you almost never have to maintain your older games. Having powerful features available to you doesn’t mean you have to use them, but it does mean you have to make an active choice to avoid them, or spend non-trivial time weighing your options. And lastly, games in the case of C/C++ (and “cloud infrastructure” in the case of Go) are a special class of application that frequently benefit from specialization beyond what can be reasonably expected of widely available generic libraries. You’ve got every hard problem in computer science, 60 times a second. I’d much rather copy/paste a data structure implementation and tweak it for my needs, than wonder why my game is suddenly slow because I triggered some other code path in some opaque generic code.

              1. 1

                Writing reusable/generic code is an order of magnitude harder than writing one-off code.

                It can be, but not always so. My strategy of writing code is often to write it cleanly with a nice architecture, so that it can either be reused as is – a simple example was a Range class I made to make some C# code work sanely - but I ended up rewriting it to be a Range with ints and a RangeF with floats to fit the other structures.

                Copy/paste is a perfectly acceptable code-reuse strategy for games,

                Copy/paste is a perfectly acceptable code-reuse strategy for ______. FTFY. In my current project, I have three controls that have a lot of mouse and drawing code in common, but it doesn’t make much sense to separate those out. So I copied and pasted the code and adapted it. But given that all three were doing coordinate scaling and translation (specifically: a full sized image, a scaled image, and a displayed scaled image that was translated from the origin into the display region to allow for rulers), I ended up making that code reusable.

                Having powerful features available to you doesn’t mean you have to use them, but it does mean you have to make an active choice to avoid them, or spend non-trivial time weighing your options.

                I think this is wrong. If I am writing a data structure in C++, I don’t have to actively avoid using template metaprogramming when making it. It just doesn’t come up. If I were writing a generic container, I would have to actively avoid using a template parameter - if it were the obvious choice. Sometimes spending non-trivial time weighing your options just means spending the time to do proper design analysis.

                You’ve got every hard problem in computer science, 60 times a second.

                I will accept this as a gross exaggeration.

                I’d much rather copy/paste a data structure implementation and tweak it for my needs, than wonder why my game is suddenly slow because I triggered some other code path in some opaque generic code.

                There is a time and place, but if you do it all the time every time you are wasting your time.

                1. 8

                  First, let me clarify by saying: I like powerful, featureful languages and generic programming. However, in my career, my preference for languages to use in engineering teams has regressed. I much rather our embedded code be C than C++. I’d much rather our services be Java or Go instead of Scala or Rust. The comments I made were about individual programming experiences, but all of them are exponentially more relevant on teams.

                  It can be, but not always so.

                  No, I’m reasonably convinced that generic code is universally harder to both write and use. There’s a major difference between being a higher-level abstraction and being generic. Generic implies parameterization, and the more parameters you have, the more that varies, the larger the design and state space, the harder it is to reason about. In most cases using generic code may only be a tiny bit more complex, but it adds up.

                  Sometimes spending non-trivial time weighing your options just means spending the time to do proper design analysis.

                  The question is whether you’re spending time reasoning about the problem and your solution; or time reasoning about a problem and the solution space. The more flexible my language, the more I find myself exploring adjacent problems to the problem I actually have. It feels like proper design analysis, but it’s false work.

                  I will accept this as a gross exaggeration.

                  No, non-trivial games are little real-time operating systems. Non-trivial games evolve in to complete dynamic runtimes with purpose-built allocators, sophisticated networking, expansive game world databases, and so on. Some of the best engineering in the world goes in to AAA games.

                  1. 8

                    I will accept this as a gross exaggeration.

                    In some cases, a game is:

                    • Handling all of the IO (like an OS, see Quake engines)
                    • Doing AI reasoning and planning (STRIPS or similar, see FEAR)
                    • Running a full-fledged JIT interpreter (see again Quake engines)
                    • Actually drawing everything (see any 3D game ever)
                    • Doing constructive solid geometry manipulations in realtime (see Red Faction)
                    • Handling NLP (see Starship Titanic)
                    • Handling crazy network sync issues (see Age of Empires)
                    • Doing weird graph traversals for scheduling dependencies (see modern MT engines with tasking systems)
                    • Doing crazy compression/NUMA/decompression with paging (see Rage)

                    So, yeah, actually, games are doing many hard problems in CS at 60 FPS.

                    1. 0

                      He said every hard problem, sure they do some, but not all. Also some of those things should be quite decoupled from the ‘60 times a second’ thing.

                      1. 9

                        For anyone reading this under anything other than --Wpedantic I’m pretty sure the statement scans just fine.

              2. 1

                I just started writing my first game. It’s for the Game Boy Advance, and I thought I’d have to up my ARM assembly game, but it’s readily programmable in C. That’s one reason I love it: I can use it seemingly everywhere. It seems like a good fit for writing games at this level, too.