460 likes | 633 Views
Yaser Zhian Dead Mage TGF 92, August 15 th , 2013. Planning for Complexity in Game Development. What This Is About. The things that you should do at the beginning of a game (or similar) project to simplify debugging, adding features, optimization, build and release.
E N D
Yaser Zhian Dead Mage TGF 92, August 15th, 2013 Planning for Complexity in Game Development
What This Is About • The things that you should do at the beginning of a game (or similar) project to simplify debugging, adding features, optimization, build and release. • We are talking about the technical and code aspect of this problem, but some of the ideas are generalizable to the rest of the team. • I can only give you some tips, not a streamlined process. That's your job. http://yaserzt.com/
Audience • This is targeted towards programmers and managers in small to medium-sized teams. • More specifically, those that are developing their own tech, using C++. • There is nothing here out of the common sense. If you are looking for some dark “magick”, look somewhere else. http://yaserzt.com/
Complexity • You have a limited capacity for handling complexity. • It’s a hydra; a thousand-head monster that grows two heads each time you cut off one. • Game developers face more complexity than most other people. • Everything is a trade-off; you trade simplicity for performance for functionality for your sanity. • And there are no silver bullets • … but maybe I can offer some aluminum pellets! http://yaserzt.com/
Process, Process, Process (1/5) • do { Conceptualize; Build; Import; Integrate; Test;} while (0 == 0); • Iteration time is the most important factor in game development process. http://yaserzt.com/
Process, Process, Process (2/5) • Ideally, you should never have to exit the game to change anything (code, assets, configuration, scripts,...) • More realistically, you should never have to exit the game to change anything except for code. • But iteration time is about more than that… http://yaserzt.com/
Process, Process, Process (3/5) • Data-driven design and code is king. • Keep engineers out of the loop. Seriously! • Design your process so you “block” as little as possible. • Code reviews are good things (even informal.) • Agile philosophy is fantastic for small game developers. • But be aware of “Technical Debt” when just implementing and going forward. http://yaserzt.com/
Process, Process, Process (4/5) • Automate as much as you can, as early as possible, and keep adapting. Tools, tools, tools! • Even small batch files (or shell scripts, if you prefer) can go a long way. • … not to mention, act as some kind of documentation. • A good automation language should have easy and powerful file and directory handling, text and regex manipulation, network facilities, etc. out of the box. • Source control, asset control, versioning, branches, tags, issue tracking and burndownare not just fancy words. http://yaserzt.com/
Process, Process, Process (5/5) • Transparency: to know what’s going on • Discoverability: to be able to fix it when things go wrong (as they inevitably will…) • Remember: in game development, the first (and sometime only) user is the developer. Design your process, tools, tech, etc. accordingly. • Beware of the “Bus Factor”! http://yaserzt.com/
Knowledge • Knowledge is power; if you don’t know what’s going on in your pipeline and engine, then who does?! • From the highest level to the lowest: • From how many characters, levels, items, etc. you have • and how many files, models, textures, etc. • to where each millisecond of each frame is spent • and who uses each kilobyte of memory • The more you know, the more you can do. http://yaserzt.com/
Naming and Directory Structure (1/2) • A name is a unifying concept. Helps develop common views and more efficient and less error-prone communication. • ??? (No one knows what you need.) • Need to fulfill at least two criteria: • Guess the function of an object based on its name • Find the object you are looking for among similar “things” http://yaserzt.com/
Naming and Directory Structure (2/2) • When choosing a name, consider how that name looks to these entities: • Yourself, in 6 months • Other people • Scripts and tools • Consistent (pancake vs. flapjack!) • Sortable (mmddyyyy vs. ddmmyyyy vs. yyyymmdd vs. yymd) • Please, spell correctly! http://yaserzt.com/
Build System (1/4) • Automatic, quick, configurable, dependable, minimal. • Think about header and source layout, very carefully and very deliberately. • The compiler is your friend. Each compile-time error is potentially one less runtime or logical error you'd have to chase down. • Ideally, wrong code (or assets) should not compile. • Static assertion, type traits, “concepts”, template meta-programming, type system design, etc.) http://yaserzt.com/
Build System (2/4) • Code build time is important. Make it as short as possible. • Plan the dependencies (inside your code.) • Use precompiled headers. • Modularize your code (into libraries.) • Use abstract interfaces (but consider the performance costs of polymorphism.) • Eliminate unnecessary inter-class dependencies. • Use forward definitions. http://yaserzt.com/
Build System (3/4) • Build variations: • Full debug • Optimized debug • Profiled release • Developer release (with debug information) • Retail • and a couple more... http://yaserzt.com/
Build System (4/4) • Strive to have a build that is always: • Playable • Performant • Progressing • In Memory • Stable http://yaserzt.com/
That ephemeral sorcery we call Code http://yaserzt.com/
Decisions to be Made • Coding Style • To OOP or not to OOP... • Concurrency and data sharing strategies • Exceptions • RTTI • Templates vs. Polymorphism • Smart pointers vs. Handles vs. Raw pointers • RAII • Lifetime and Ownership Management (and a bag full of problems) • Intrusive code instrumentation • String format • and much more... http://yaserzt.com/
Performance • Premature optimization is the root of all evil, • But… • Writing high-performance code is an uphill battle; miss a step and you end up at the bottom. • Misarrange your data structures and you are in the 2-frames-per-second limbo forever. • Data (and by association, memory) is the most important factor in performance. http://yaserzt.com/
Love Thy Compiler (1/2) • Use everylittle obscure compiler and platform facility that can possibly help. • But wrap them up, even if you don't care about portability (in case of a change in compilers/libraries, or if you want to disable a feature, etc.) • Don't be afraid of assembly. Learn to read through what your compiler produces. Love doesn't necessarily imply trust! http://yaserzt.com/
Love Thy Compiler (2/2) • Learn about compiler #pragmas and intrinsics. • Use intrinsics instead of inline assembly, because asm usually hinders compiler optimizations. • Learn about pointer aliasing, restricted pointers and every other hint that you can give the compiler (including const) to produce better (faster) code. • Learn about your machine architecture and OS idiosyncrasies. http://yaserzt.com/
Type System (1/5) • Consider implementing your own containers (STL can be too frivolous with memory.) • Plan for type metadata and reflection. • Try not to use common type keywords (int, short, float, etc.) Typedef them all. • i32, u32, i16, u64, i8, u8, byte, ch8, ch16, ... • f32, f64, v128 • ..or use <stdint.h>, (actually, <cstdint>) which is a standard header. http://yaserzt.com/
Type System (2/5) • For types that are equal physically, but not semantically, e.g. • angles in degrees and radians • time in seconds and milliseconds • normalized vectors and general vectors • “Hungarian Notation” can help (commonly misapplied, e.g. in Win32 headers and docs) • degAngle = radAngle; is most probably wrong and the good thing is that it indeed looks WRONG. http://yaserzt.com/
Type System (3/5) • Better yet, use different classes with no assignment operators (or at least correct ones.) • There would be no (implicit) conversion from float to Degree or Radian, • So, code like Rotate(45.0f) won’t compile • You’d have to write Rotate(Degree(45)). • The constructors and conversion operators for Degree and Radian classes will do the correct multiplication. http://yaserzt.com/
Type System (4/5) • It can also help with performance in some cases: • If you have Vector3 and NormalizedVector3 classes, you can overload on them and save a normalization while keeping the same external interface:void Do (NormalizedVector3 v) {// You know the vector is normalized...}voidDo (Vector3v) {DoSmthng (normalize(v));} http://yaserzt.com/
Type System (5/5) • Strings: • ASCII is dead (mostly.) • At least differentiate between these usages: • File names and paths. • User input and text shown to the user. • Internal entity names, IDs and the like. • Text file and script contents, etc. • And more. • Realize that some strings are (mostly) constant during their lifetime. (Optimization opportunity!) http://yaserzt.com/
Pointers (1/2) • Don't use T* to represent a pointer to T. At least typedef something like TPtr and use that instead (or Ptr<T>.) • But consider the loss of transparency you incur when using TPtr instead if T*. • Don't use T* to represent an array. Use a struct with size information (templated for arrays with known sizes at compile-time, or a member size_t (or smaller data if suitable) if not.) • Or better yet, use std::array<T>. http://yaserzt.com/
Pointers (2/2) • NULL, nullptr, the integer 0 and '\0'are different things; don’t use them interchangeably. • NULLis not the only invalid pointer value (0xcdcdcdcd, 0xcafebabe, 0xdeadc0de, …) Don't use comparison with NULL to detect this. Use an inlined function or a macro. • Don't just use NULLto pass optional values around. You can use std::optional (from C++14,) boost::optional, or write your own. http://yaserzt.com/
Standard Library Independence • File Handling and asynchronous I/O • Directory handling • Random numbers • Math • Time handling • ... http://yaserzt.com/
Debugging • Consider the idea that the purpose of writing code is not running it. Write code for debugging. • Logging • Assertion • Context metadata • Crash handling and crash dump • Visualizing data http://yaserzt.com/
Logging • Do notoverwhelm! • Collect as much as possible, show as little as needed. • A good logging library should be: • runtime controllable • able to send logs to multiple targets • able to filter what is sent where based on audience, source, target, etc. • fast, fast, fast • as non-intrusive as possible • Employed from day 0. http://yaserzt.com/
Assertion • You do use them, don't you? • Chances are, you are doing it wrong anyways! • Never use the standard assert. • Assert whenever you can, but nowhere you shouldn't. (Hint: don't assert when a configuration file is not found; make a new one!) • Assert-and-repair http://yaserzt.com/
Context Metadata • Should be totally and partially disable-able. • Including, but not limited to: • Machine name, user name, code revision, build number, build parameters, time, environment, frame number, etc. • Where does the execution flow come from? (Stack trace) • Where are we now? File, line, function, instance. • Which subsystem called this piece of code? Graphics? AI? Animation? Sound? • Why did it call this? Was it initializing? Loading data? Looking for a collision? Walking the scene graph? Visiting AI actors? • How long did the function take to execute and how many times was it called in this frame? How many memory allocations did it perform? • … (use your imagination!) http://yaserzt.com/
Crash Handling and Crash Dump • If you have to fail, fail as soon as possible and as loud as possible. • Gather as much data as you possibly can: place, time, what happened before and was going to happen after, context, hardware environment, software environment, user settings, etc. • Use a “Black Box” type construct. Collect some data all the time, but store it only when crashing. • Automatically submit this info to your issue tracker (during dev) or emailing it to your team (after release.) http://yaserzt.com/
Visualizing Data • There is much more going on in a game than graphics (which can be seen already.) • Lots of data involved. • No clear picture from mere numbers. • Hard to understand in a single slice of time. • Add visual representations for non-visual objects: physics, bounding volumes, AI algorithms, behavioral decisions, etc. • Real-time graphs and bars too! http://yaserzt.com/
Memory (1/2) • Neveruse naked new/delete in real code. • Never everuse naked new/deletein real code! • Write your own memory manager, even if it is just a wrapper over new/delete. • Don't forget malloc() and friends. • Don't forget third-party libraries. • Always be conscious about who uses how much memory. Always. http://yaserzt.com/
Memory (2/2) • Always override the memory management in third party libraries (including STL) unless you have reason not to. • Writing a memory bug detector is quite easy. Write one or find one. • Memory allocation is usually a source of performance bottlenecks as well as bugs. • If you think garbage collection is the panacea for your problems, think again. http://yaserzt.com/
In-Game Console • Useful for debugging and interactive experimentation. • Tied-in to the scripting system. • Make it like a good OS shell. • Can think about it as a higher-level debugger (inter-object relationships, etc.) • Give access to everything you can, but consider that the designers and artists will have to use it too. http://yaserzt.com/
Serialization • Useful for much more than saving/loading or network communication. • Template magic? Some form of IDL? Übermacros? • Consider two interchangeable formats: one optimized binary and one human readable. • Make everything you can serializable. • Consider interactions with source control. • Plan for evolution of data structures over the course of development. http://yaserzt.com/
Runtime Asset Management • Everything other than code that constitutes the game. • Consider going beyond simple files. • Consider treating asset management more like writing a DBMS, than a file system. • Consider integrating asset fingerprinting, change management, versioning, change detection, hot reloading, etc. • Consider asynchronous operations. http://yaserzt.com/
Time • Do notunderestimate time management. • Never just use direct calls to OS to get the time for game subsystems. • Familiarize yourself with higher-resolution timers (RDTSC, QueryPerformanceCounter(), clock_gettime(), ...) • Familiarize yourself with multi-core issues, power management issues, Windows issues(!), user issues,... • Consider the difference between wall clock, game clock, camera clock, level clock, animation clock, etc. • The more time you put in time, the better the times you'll have when the time of debugging time comes! http://yaserzt.com/
Screen Capture and Game Replay • Capturing still images is a must. • Capturing video is a big plus. • Capturing metadata (DEM files, anyone?) to be able to replay a scenario is a huge plus. • The game replay subsystem can be implemented with varying levels of complexity and functionality. Even a simple system can have considerable benefits. http://yaserzt.com/
Runtime Statistics • Please invest in this! • You must know your own game. This is one very good way. • Anything from frame-rate and tri-count to number of memory allocations and file accesses per frame, per second or per-level. • Find interesting ways to display these data. http://yaserzt.com/
Automated Testing • Full unit testing is the Holy Grail! • Testing of games and features is usually very time consuming. Any automation helps. • Any change in code or assets can have undesired side-effects: regression testing. • You can even have automatic screen-shot comparisons! • Auto test non-visual parts, if you can't manage more sophisticated methods. http://yaserzt.com/
Contact us at http://deadmage.com/ And me at yaserzt@gmail.com "I know that you believe that you understood what you think I said, but I am not sure you realize that what you heard is not what I meant.” Any questions?