Hacker News new | ask | show | jobs
by optionalparens 3448 days ago
I can see how it might be fun to do something like this for a blog post or simple project, but in a real game this just doesn't work for many reasons, most listed here already.

There are tons of ways of doing dialog system. Mostly it comes down to your specific game and I highly recommend against generic dialog systems. Rather, the most you can do at a generic level is try to come up with a good way to store dialog and retrieve it in the ways you need. This isn't the best thing I've seen or worked with, but here's an easy to understand and documented method and presentation about it form Valve:

http://www.gdcvault.com/play/1015317/AI-driven-Dynamic-Dialo...

Anyway, I've been programming games and game engines for several decades, and coroutines are always in the list of things that you can throw in the junk pile along with other cool things like continuations, various theoretical data structures, fancy dispatch mechanisms, events/signals, application-wide functional purity, etc. Of course all of these things are useful, but they tend to have problems that make them of limited use in professional game development.

Here's a brief checklist of show-stoppers off the top of my head I tend to go through before introducing anything "creative" into a game, and especially a game engine. Most of the aforementioned items fail one or more of these.

- Performs awful in common cases or when applied to actual game-like conditions vs. theoretical

- Murders cache lines

- Hard or impossible to debug

- Hard to save, load, serialize, deserialize, or snapshot

- Doesn't work over networks

- Doesn't play nice with libraries or data coming from other libraries or languages (ex: 3rd party physics lib)

- Unpredictable execution start or end times

- Non-determinant memory usage or allocation patterns

- Risk of stack-overflows

- Non-portable or problems on specific target architectures

- Requires "religion" meaning it infects all your code or forces you to write all of your code a certain way, everywhere

- Fragments memory

- Long blocking or uninterruptable processing

Conversely, besides the obvious opposites of most of the above, I look for the following when introducing some major data structure or conceptual item like coroutines to a game or game engine:

- Leads to predictable, consistent code and resource usage

- Data-driven

- Editor friendly (as a corollary to the above)

- Clear debugging story

- Both simple and easy if possible. No, they are not the same.

- Plays well with the world around it

- Portable

- Fast

- Loved by the CPU, GPU, and/or compiler, and produces reasonable assembly on target hardware

- Easy for someone else to jump in and understand the flow and how it works, assuming they are not a moron.

Pretty much these two lists mean that games tend to be made of meat and potatoes things. Simple data structures like arrays, custom allocators, predictable branching and execution patterns, up-front memory allocation, and CPU and GPU loving goodness. Pretty much anything stashing things away and building up massive states or jumping around all over the place, pointer chasing, and so-on should always be a no-go.

Anyway, if you're building a truly simple game, as in a 2 day sort of affair, do whatever you want. If you want to build something even remotely complex that you will work on for awhile, it's best to do things the right way which means detach yourself from everything you think you know about most of computer science and think in terms of pipelines, CPU instructions, caches, and predictability among other things. This often means programming against the grain of your language a bit, throwing out sugar, std libraries, and all kinds of things. It really sucks, but that's the essence of good game programming. I hope one day it will be different, but as long as there's still need to push gameplay and graphical boundaries, professional developers almost always need to squeeze out what they can.

3 comments

>Anyway, I've been programming games and game engines for several decades, and coroutines are always in the list of things that you can throw in the junk pile along with other cool things like continuations, various theoretical data structures, fancy dispatch mechanisms, events/signals, application-wide functional purity, etc.

This is one of the worst outlooks on programming I've ever seen. Did you know that any control flow operator such as try/catch is really an application of continuations?

Not sure what your point is here, could you elaborate? It seems to me maybe you are missing the point.

As I said, this is about programming games, primarily in C/C++ or a related language (though applies mostly to others), not programming in general. If you enter into other high-performance domains, you will encounter the same issues. Conversely, other domains such as general application programming do not adhere to this.

Regarding try/catch, I am well aware of low-level implementations in various languages. Did you know that in most game engines, you don't use that much try/catch for this exact reason? If you're littering your game code with try/catch everywhere, you are doing something wrong. Not to say you don't have any, but there are other error handling and return methods or you just let things bubble-up and handle them there. What would you actually do in most try/catch situations deep inside a game loop outside of things where resources can leak horribly like IO?

When you're working on a game, there are of course areas you can compromise. It also as I mentioned depends on the nature of your game. I'm speaking strictly of properly designed, professional games that have to be competitive in terms of features and run at a smooth frame rate while doing many things (i.e. your average AAA console game).

In case you still doubt what I am saying, go look at GDC talks, game dev papers, etc. and you'll see the same thing. As far as someone people around here might be more familiar with as a source, I seem to recall there were multiple times Jonathan Blow mentioning exactly what I did (maybe a talk he gave at Berkley?). Anyway, the point is that as programmers we learn all these cool and fascinating things, but in game dev, you tend to have to let go of "cool" things and spin your thinking a bit towards things like predictability, performance, stability, transparency, and composeability.

Change "is an application of" to "can be badly implemented by, with serious issues" and you're just about there.
please keep in mind. this is a dialog system.

- murders cache lines is clearly not important for a dialog system.

the use of lua fixes a lot of things you mention in terms of stability, ease of use by other people, etc.

data driven is the most interesting one.

using lua to drive game logic allows you to be less data driven. especially if the lua code is like in this article -- use of very high level lua functions.

the code now looks more like DSL which is more powerful/flexible then using data driven approaches.

Please re-read what I wrote. I said 1 or more of these items, not all of these items. Additionally, please read my reply elsewhere in this thread about speed in dialog engines where I listed out why, where, and when speed does indeed matter for dialog engines (thus cache lines). Moreover, the last thing you would want is for dialog of all things to murder your cache when it one of the easiest places to optimize for the cache in an entire game engine.

Lua is indeed used to fix many things in game engines, but not really most of what I listed. The top reasons scripting languages tend to be used on top of other languages like C/C++ in games is to avoid lengthy recompilations, provide an entry point for user-editing/plug-ins, serve to some degree as a more RAD tool, and build a DSL for scripting events.

You are correct data-driven is interesting, but your conclusion is 100% wrong. The entire entity component system approach and the general trend towards functional paradigms couldn't be more at odds with you. I can't think of what might be the best resources off the top of my head for you to read, but there's plenty out there on ECS, functional-style C/C++ as related to games, and low-level engine design stuff on older blogs like the one BitSquid wrote years ago.

The entire industry is moving to trying to make games as data-driven as possible. This is as you point out the anti-thesis of what you are saying. Further, your point is really bad advice for any game programmer. Making things less data-driven in general makes it infinitely harder to debug, cache, build editors around, send over a network, save/load, update/migrate, share between threads, and be consistent.

What you are proposing would make you fail an interview with any decent game developer or get you fired. You would drive a tool designer insane as well. Huge chunks of games are built by creating tools with code to manipulate data, not code. That data often lives external to the code entirely, whether it is an excel sheet, custom binary format, custom database, or something else. You want as little as possible in the code and nothing that will affect compilation time. Moreover, you need to be able to also do things like translations and internationalization, not to mention add new assets, hot-swap things during dev, and so on.

In other words, no, building a DSL in Lua is not more powerful as you describe. It could be powerful if it was data-driven, anything else simply would not get used in a professional setting with decent developers. I don't understand why sometimes on HN people want to graft general programming or web dev on to games. That's the only way I can explain this kind of thinking, and even data-driven approaches are becoming more prevalent in these areas.

regarding cache lines: if i understand correctly, the scenario you are optimizing for is that your engine carefully needs to be in control of all cache lines to met the performance requirements. any "rogue" component like a dialog system, can destroy this cache layout and hence the performance guarantees are not met ?

regarding data-driven vs lua.

my point of view is data == code. except that the guarantees you can provide when using code are less but you get more powerful expressions and flexibility in return. ( ==> turing completeness ) the more you are willing to give up turing completeness you get to the "data driven" side. The stricter and less powerful your data is, the better you are able to guarantee performance and other requirements. however this comes are the cost of loosing flexibility and agility.

i agree. what i am proposing works in a small team (<10) where everybody does everything. this doesn't scale to large teams. (or rather i haven't seen this in working in large teams)

Again, childish downvoting by someone here.

The term component here would be incorrect, rather "system" would be a better term.

There are many variations and approaches to what I am talking about so I'll stick to one very common approach used by a lot of studios and engines today. A typical more cache-friendly update cycle goes like this, calling "update" as a function of time/state on each system:

System A -> System B -> System C -> System D -> etc. (loop and repeat each frame)

This cycle sometimes skips certain systems for either performance or sim reasons. Each system tends to have inner loops and the overall system loop is often broken up a bit into sub-loops or separate loops, ex: render vs. physics for purposes of separate sim-time looping. The idea here is you do a single operation over and over again on the same type of data, avoiding lots of pointer chasing, v-tables, and so on along the way. The code branches very predictably and it becomes easier to avoid loading data into the cache that will blow it up or not fit into a page.

What makes this friendly to a data-driven approach is if you're working mostly with systems that operate on pure data, your update each frame from a purist point of view approaches a functional reduction. In practice, there are certain things that don't fit perfectly into this paradigm, but the idea most engines take is this approach + cut corners when necessary.

I am contending that your point data == code is wholly false. Again, this is well-known in computer science to the point where your argument only stands if you're looking at code from a very low-level point of view. Again, all current game development trends refute what you are saying - almost every year there's one or more GDC speaker sessions about this topic, see the GDC vaults, see Unreal Engine, Unity, CryEngine, etc.

Yes, to a degree code can be data, but it becomes exponentially difficult to manage and process vs. simple representations that tend to be language, runtime, and network agnostic - i.e. data structures. Lua tables and other things inside Lua are not the same (at least not until serialized to a more language agnostic format), especially in the face that in most real game dev situations, you're passing data between tools and languages, and in several different flavors of run and design time. I've done quite a bit of code as data approaches to know that even when it can be done, it has many drawbacks - see object databases, Smalltalk continuation serialization, code gen tools, etc.

Functional languages and several more fringe languages tend to be closer to code == data, the most wildly known and regarding being Lisp-like languages. Typically these languages have a minimal amount of abstractions and power things like first-class macro systems. Even in the face of macros, there are many drawbacks to dealing with code this way in general, and even more in a game. For instance, restoring state exactly as it was and is, and using that state in the face of updates to the game itself are paramount to the point where few games can survive without it otherwise. Data makes this easy.

What you are asserting as more power is exactly the opposite and I'm not sure how I can make this any clearer. Your code can freely operate on data any way it wishes, not the other way around. Code operating on code is generally a bad idea which is why clever meta-programming techniques and code generation are things you would rarely see in any game engine unless it was a hack or a bad implementation.

You are losing little by making things a data representation and gaining much in terms of what I already listed. All the benefits of data are far more valuable for game development and I am not sure what cannot be achieved by forward-loading or pipelining data into an asset pipeline and operating on that data doesn't achieve that this does. Beyond performance, the other benefits like editors, network transparency, easy loading and saving of state, and so on make it easier and faster to develop even small games.

What you are proposing is a solution looking for a problem. I'm not denying it's a fun thing to do, especially if we're talking about a regular "app" rather than a game. Personally, I like coroutines, fibers, CSP, continuations, multiple dispatch, and other fun CS tools but not for game programming in most of the time, and certainly not at the level of an important game system. I am asserting that in the game dev world, treating code as data is a really bad idea, which several other people have also pointed out here. Your solution adds very little, while adding unnecessary complexity to check a "cool" computer science box.

In case anyone is interested in the fine talk mentioned, there's video version on this link:

http://www.gamasutra.com/view/news/198377/Video_Valves_syste...