What Programming a Game in 48 Hours Taught Me About Programming Games
jeffwofford.com
jeffwofford.com
While the article lists many plausible reasons why the pace of work turned out to be faster on the shorter project, there is also one that is not mentioned but perhaps the most important: complexity doesn't scale linearly. It's always faster to write the first 1000 lines of code from scratch than to add 1000 lines to a project that is quite large already, mostly because each newly added component has a growing chance to interact with an ever increasing number of older components.
This effect is particularly insidious, because often the long slow complicated thing that the product manager wants is just the sort of interesting challenge that developers enjoy. They don't point out to the product manager that there is a simpler solution, because that would take away the shiny toy. LD does not have this problem because the developer knows that she can't allow herself to go off on a 3 week exploration of compiler theory, because otherwise her product won't be ready in time.
But I am not dismissing your argument on the delay imposed by the extra layer between the project manager and the developer, specially if they have no programming experience.
I agree that complexity doesn't scale linearly. That's a very insightful thing to note. However, the fact that LOC correlates with "probability of a change causing unintended consequences" in many codebases is coincidental rather than causal. The underlying cause of the unintended consequences is almost always poor structure and/or poor planning.
Two words: cohesion and coupling.
Highly cohesive systems which are loosely coupled are far easier to extend and modify without fear of weird and unintended "spooky action at a distance" interactions.
1. Each module necessarily has access to functionality provided by all its "ancestors". In the best case, this number still grows logarithmically with project size, and accordingly the difficulty of adding or modifying code in a "leaf" module.
2. New libraries and language features are generally seen as a neverending bonanza. But before you can effectively modify a module you need to understand how it currently works, which necessarily involves understanding the external tools used in it (quirks and all). As the variety of these external tools increases with code size (at quite possibly a super-linear rate), the complexity of working with any given module deepens accordingly.
The Holy Grail of software development would of course be perfect layering: each layer completely masks its predecessor, and is no more complex. Someone who achieved it could indefinitely roll off as much useful code on day 500+ as they did on day 1. But I'm not aware of any practice or methodology that dares to even remotely promise such utopian benefits.
I didn't explain clearly enough that I agree with your assessment that as a codebase grows the effort involved in maintaining/adding to/modifying it grows as well. However I disagreed with what you said was the main reason for this. I think that your "main reason" is definitely a contributing factor, but it's too complex to be boiled down like that.
I also agree that maintaining a properly structured project definitely takes considerable effort. Further I'd argue that when this is done well the effort is largely front-loaded. This means that as the program matures, the effort required to implement new features of a given complexity will largely taper off. I believe this is the best-case logarithmic growth you're talking about.
Obviously worse cases would be linear, quadratic, and so-on. I view the differences between these curves as essentially the amortized value gained by doing this kind of front-loaded architectural work. If you get it perfect, you gain the difference between log(n) and perhaps some kⁿ, depending on project size/scope.
Speaking of scope, this ideal is only achievable where project scope is relatively well defined and relatively static throughout the life of the project. Sure, you can make a spam filter play chess, but, well, you get the idea...
And yes, leaky abstractions, complicated tools, all of that definitely contributes in horribly awful, ugly, hard-to-predict ways. If you care to read my thoughts on this topic in particular, I think I summed them up nicely in my reply to this comment [1] on the short-lived topic "Why I hate Frameworks."
Boiling all of this down, I think all I'm really getting at is that complexity drives effort, but there are ways of minimizing the complexity of large codebases. Sure external forces often make this minimization effort obnoxiously difficult, but it's still possible. While we might disagree on what "often" really means in the prior sentence, I think otherwise we're wholly on the same page.
Anyway, I'll try to go change my pants...
On the other hand companies generally put their very best developers on new products and features - i.e. writing those first 1000 lines. While the mediocre devs get to extend and maintain those products/features, by copy paste or what got you.
It's remarkable that modifying C++ headers is such a chore that it comes up as a reason for getting something done in a tiny fraction of the time for a roughly comparable task.
Couldn't agree more. Though I don't find it all that painful - lots of editors/IDEs have a 'go to header file' shortcut that makes this take a couple seconds at worst.
But then I have repeatedly wondered why IDEs don't seem to have a way to synchronize your cpp changes to your header automatically. Rename function in .cpp -> automagically rename function in .h. That's all you really want a lot of the time.
In addition changing a header file can at times trigger a lengthy build.
Visual Studio and ctags based 'IDE' style features allow you to go to definitions or declarations fairly easily.
Unless I'm mistaken I think the eclipse CDT for c++ can change/refactor file and header as you wish for. [1] [2]
[1] http://help.eclipse.org/helios/topic/org.eclipse.cdt.doc.use...
[2] http://r2.ifs.hsr.ch/cdtrefactoring
I'm unaware of anything that can change function prototypes correctly, but maybe eclipse CDT can do it.
Well yeah, but that's true for doing it by hand too. If it's that slow, you won't have automatic builds on anyway, and you can usually cancel it if you need the CPU for some other reason (in which case, again, why do you have automatic builds on).
And even if it were only a 90% thing, that's a 90% savings. I mean, literally, if you change the name alone, change the header name. You get immediate notification that other locations might need changing if/when the build fails. Similarly for argument order / name (type will probably cause build failures).
But all those build failures would happen anyway if you changed it by hand, so it's not any different, just faster. Isn't faster the goal here?
Changing the header name sounds like a reasonable suggestion, but I don't think it's a goer with large projects.
The problem with headers is that as soon as you are doing anything beyond trivial you can get in to a big fight with the compiler, leading to the following sorts of problems:
http://stackoverflow.com/questions/12573816/what-is-an-undef...
When these problems are C++ template related, then you have to remember exactly what you changed and where because even on the intel compiler, the compiler isn't really going to tell you what's wrong.
link taken from here: http://stackoverflow.com/a/13840863
C++ has no future by design.
http://en.cppreference.com/w/cpp/language/except_spec
The fact this is deprecated in c++11 is my personal pet peeve.
I can't reason about what the impact is without putting it in to practice, but given that exception safe C++ can be hard to write and it's deprecated does that mean it's going away in C++14 and what are the implications of that?
The throw() specifier (counter intuitively meaning 'should not throw anything') is everywhere! If it turns out that all the advocacy for using exceptions has resulted in written code that uses deprecated conventions and requires maintenance and re-understanding, then I will be very very disappointed.
C++'s design combines the worst of checked and unchecked exceptions and adds some additional run-time overhead for good measure. Writing exception-safe C++ code is essentially impractical for large C++ programs that include C libraries unless you write your own RAII classes for everything. But then your C++ code is littered with unreadable std::shared_ptr<whatever> everywhere. If C++11 had adopted some Rust-like shorthand syntax for std::shared_ptr and std::unique_ptr, it might actually be palatable.
are you sure about that [1][2], this is what throw() was deprecated for:
[1]http://en.cppreference.com/w/cpp/language/noexcept
[2]http://en.cppreference.com/w/cpp/language/noexcept_spec
Unless I am mistaken, on 32bit platforms exceptions incur a runtime overhead but for 64bit zero cost exceptions were developed, and only incur overhead if they are triggered [3]
[3] http://gcc.gnu.org/onlinedocs/gnat_ugn_unw/Exception-Handlin...
I think that any calls to the C library can be handled as followed in an exception safe manner unless I misunderstand you:
EDIT:( I misunderstood you, you meant wrapping resources, what follows is nonsense as a C library isn't going to throw any exception)
void library_function_wrapper()
{
try
{
library_function();
}
catch(...)
{
}
} void f() throw(X, Y)
{
int n = 0;
if (n) throw X(); // OK
if (n) throw Z(); // also OK
throw W(); // will call std::unexpected() at run-time, not a compile-time error
}
[1] http://en.cppreference.com/w/cpp/language/except_spechttp://www.youtube.com/watch?v=f90R2taD1WQ
His main point is that building a game is no longer about what you can do, but rather what you can get done.
There are no external constraints on my work. When one artificially arises, I get my best work done, quickly. It doesn't burn me out because a rest comes afterwards.
I have no idea how to replicate this urgency consistently.
The nice thing about it is that it stops you from getting distracted on a constant basis and you have something done at the end of your sprint.
Also reading up on perfectionism might be interesting (hint: it often doesn't end with a perfect result but it is always time consuming.) YMMW.
Much better to eliminate distractions, in my experience (selective site blocker, headphones with white noise or coffee noise or wordless trance, and an IDE that never stalls.)
Why I hate Eclipse
I don't think I'm a perfectionist, and agree it is to be avoided.
His point about metawork really hit home hard. I'm at the end of a 4-year project now, and I'd estimate 90% of what I'm doing is what he calls metawork.
The major takeaway I got from it was that I should try doing some 48-hour projects myself - or perhaps find some 48-hour film competitions to enter - and see what the results are.
And, of course, write them up as a blog entry.
I discovered this when I started building game ideas in Javascript instead of Xcode. I'd have a skeleton pounded out in an hour or two, and I'd add features in minutes instead of hours. I took a list of things that I though would take me the weekend, and I completed them in an evening.
http://strlen.com/java-style-classes-in-c
You write all the code for your classes inside header files and include them inside another class in main.cpp. That way you get around "declare before use" rule using uncommon feature of C++.
I want a tool where I can do this but in JavaScript on a canvas.
Makes me wonder if it would make more sense to cram a 40 hour workweek into 3 days instead of 5. I think it'd be more enjoyable as well.
C++11 has been around since `past several years'!?
Yes, but it used to be called C++0x and the official spec was still in the works. However, you could actually use most of the features in GCC and some other compilers years before the spec was out.
I admit that, for Java, the gain in language expressiveness isn't worth taking the performance hit, because the former is so minimal. On the other hand, moving to a truly high-level language with half-decent performance (e.g. Ocaml, Clojure) might ameliorate the schedule slippages for which that industry is known.
I do agree, however, that this doesn't make C/C++ necessary for game programming. That's probably why modern game engines let you write a great deal of the game logic in a scripting language.
Of course, most C++ code I see - even in language performance benchmarks - doesn't vectorize either. g++ isn't particularly good at it and most people don't know how to do it.
Games are made in iterative process, you change sth, test, etc. You don't know if something is fun and looks good without testing it. And it's really hard to upfront design for fun.
Without side effects most of the time programming in this way is spent maintaining long arguments lists and passing them around to be able to add even very small features.
You want to play sound when player leveled up? Pass soundManager through this 7-functions-long callstack. Now you also want to shoot stars around the player on the occassion? Pass this ParticleEffect handle too. Oh, but the effects should vary depending on the region the player is in? Pass that information to that function too. And time of the day, while you're at it, so stars can be blue at night and yellow at day.
In the end you give up and pass WorldState variable to every function. I don't think pure functional programming is good for prototyping games.
I thought about this problem a little, and I think this could be solved by structuring the WorldState variable into a tree, and writing a script that you run after the game is done, that looks inside functions for which parts of the WorldState they really need, and changing them accordingly, so the code is easy to read and functional after the experiments are done.
Edit: Here's a guys experiences from making a game in Clojure a year ago:
http://stackoverflow.com/questions/9755882/are-functional-pr...
http://clojurefun.wordpress.com/2012/09/03/ironclad-steam-le...
> Long parameter lists – I struggled a bit with this, and still don’t have a great solution.
Of course, Clojure has wonderful concurrency as well, so you can ameliorate the cost of using Clojure by spinning up more threads. However, games are a tricky thing to parallelize, from what I know. Especially as the only place you can actually make OpenGL calls is from the thread a program is started from.
I wouldn't recommend Ocaml for games though. Use Haskell or Go instead. Ocaml might be speedy, but it has a GIL, a la Python or Ruby. Also as far as I've found, there are basically no bindings libraries out there for opening windows or making OpenGL calls. And I might be misremembering, but the libraries that did allow you to use OpenGL used 1.x or 2.x versions, meaning you can't use a modern pipeline.
There is nothing worse in LD than seeing the voting screen filled with Windows binaries you don't want to download.
Here my entry this time if anyone's interested ;)
http://www.ludumdare.com/compo/ludum-dare-27/?action=preview...
For graphics or physics it's easy to make a demo - you render a spinning buddha or simulate a hundred balls, show how much CPU and GPU it takes and everyone can extrapolate this to the full game. I don't know what could be a sufficient prove of Ocaml viability other than a full game written in Ocaml.
I am not familiar with indies or mobile but I imagine, they are even less likely to risk as their chance for success is already pretty low and it would be rather reckless to add more unknowns from an untested technology.
AAA game engines are still all about performance and tight memory budgets due to the nature of the platforms they're developing on - consoles and PCs. Hence the aversion to garbage collected languages, although higher level languages usually make it in the engines as scripting languages for game logic: UnrealScript for UDK, C# and UnityScript for Unity, Lua for CryEngine... The industry is traditionally oriented towards C++ and imperative / OO languages, but there's a lot of potential for functional languages in the "embedded scripting" area. In his 2013 QuakeCon keynote [1], John Carmack talks a bit about his experience with functional languages, and how, for example, Scheme could be an ideal candidate as an embedded scripting language.
As far as smaller teams are concerned, and especially indie development, there is a lot of potential for OCaml, Clojure and other functional languages. Many mobile and indie dev teams already use a heck lot of C#, Python, Java and other specialized tools like Haxe, ActionScript. But all these languages have well liked and mature frameworks geared towards game development (XNA / MonoGame, PyGame, OpenFL, libgdx, ...), which is maybe what Clojure and OCaml are missing right now.
You can also check out the nice wrapper around the Processing library called quil: https://github.com/quil/quil
It is a framework developed around LWJGL for the desktop and also has an Android backend. libGDX lets you use OpenGL without worrying about how OpenGL works.
http://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-...
Lambda the Ultimate has more discussion of the talk, including comments from Tim Sweeney: