More Dirty Coding Tricks from Game Developers (2010)
gamasutra.com
gamasutra.com
> - Anonymous (sourced from Reddit replies to the original 2009 Dirty Coding Tricks article)
So not anonymous at all then. reddit comments have usernames attached to them, URLs that can be linked to, and even an on-site messaging system that can can be used to ask the author for permission to use the content. Heck, every comment has an "embed" link below it to conveniently embed the properly cited comment right on your page.
Is this a thing that's okay to do? Take someone else's content from someone else's website, copy-paste it into your article, and claim it was "anonymous"?
Would this author be okay with it if I copy-pasted their articles to my blog? What about if I stole all of the comments posted to their articles to make my site look more active?
It's greatly amusing when someone just gives up all pretense and just scrapes a subreddit, then SEO blog spams every single post - because then the members will upvote stories about how terrible the website is. I unfortunately can't find anything specific at the moment, but 2 of the big ones I remember was /r/makeupaddiction and /r/gaming.
EDIT: For the curious, here's the mod post about the /r/makeupaddiction incident https://www.reddit.com/r/MakeupAddiction/comments/39o74y/mod... I'm unable to find the others at the moment, though there is a list under the top comment on the thread I've linked.
[0]http://www.reddit.com/comments/9ch0s/dirty_coding_tricks_nin...
Besides, he has to have fetched them at some point and he should have taken the cites at the same time. "I found it somewhere on reddit" just isn't enough. It makes me wonder where he stole the other ones labelled "anonymous"
A friend of mine was having crashes with a program. When debugging it he found that a bunch of his local variables had weird values. He suspected that there might be a buffer overflow elsewhere that was trampling over the memory.
The fix? Just add "char buffer[ 1000 ];" to the function to absorb the damage. It fixed the crash and it shipped like that.
See "The Programming Antihero"[1].
[1]. http://www.gamasutra.com/view/feature/132500/dirty_coding_tr...
The other similar urban legend is the enterprise teams which have for(int i=0;i<10000000;++i); loops in code which they can remove a zero when performance boost is needed and they can quickly provide a fix.
Ugh.
> Personally, I hate CONST with a passion, so I like this trick.
Ugh.
> PhysicalObjects could take damage. The air strikes did enough damage in a large enough radius that they were quite literally "killing" the camera. I did fix the bug by ensuring that cameras couldn't take damage, but just to be sure, I boosted their armor and hit points to ridiculous levels. I believe I can safely say we had the toughest camera in any game.
Awesome.
A similar seeming bug is the old "kangaroos with rocket launchers" story (from military simulations, not games) [1].
Unfortunately that approach is rather cumbersome to reason about and brings with it unhandy syntax requisites. Given an entity UUID, how do you get a value for some property? It would be component_table_get(health_components, entity_id)->hitpoints. Or something similar. (note: what happens if you accidentily call that function on a entity_id for which no health_components entry exists?)
The alternative which is really fine for smaller games and which often leaks into the codebase of larger games, sometimes for performance reasons is to make the entity a struct which has some members common to all entities. Now you can do entity->hitpoints, but the downside is that every entity, including abstract game components such as the camera also have hitpoints.
If you're curious why games don't do inheritance (anymore) look up articles about Component Entity Systems, they usually do a good idea of explaining why games are too complex for inheritance.
I work with Unity engine (which uses exactly that model), and in good code these problems don't arise — or, rather, these checks are meaningful and actually represent the logic, rather then being "unhandy syntax requisite".
Imagine that you have a lava object with a trigger collider (that means that it doesn't influence the physics) and a Lava custom component (MonoBehaviour) on it. In `OnTriggerEnter` method of Lava you run `var health = GetComponent<Health>()` method on GameObject that entered lava and determine if that thing is something that can be damaged — because, quite possibly, it is a camera (as in that example) and the method will return null. And then, if it is something that you can damage, then you can use the `health.Damage(5 /* random dice roll */)` on it.
In some game, menu was written on stone tablets. Due to the bug tablets received guard AI and would randomly walk away to patrol the island.
> Ugh.
Gamedevs gonna gamedev.
Friends don't let friends printf(str).
I saw a couple of his introductory videos and I think he is great to teach specifics about game programming, but should definitely not teach how to write C.
Note that I'm not saying "don't use 'const'" with the above (and I'm CERTAINLY not saying "using '#define const' in your code base is a good idea"); only that 'const' in C-family languages has significant drawbacks which may affect the engineering tradeoffs you make when you decide whether to use it.
By contrast, languages such as Ada have explicit and implicit constantness restrictions ('in' parameters, for instance, cannot be modified), but these do not modify the type of the object, allowing objects with these restrictions to be type-compatible with objects without them.
AFAIK this is pretty much limited to C and C++. Many (and I am willing to venture most, or at least the most-used) C-family languages that aren't C/C++ don't suffer this issue. Go and Javascript, for example, both use "const" in a way that doesn't modify the type of the underlying object.
Although it's been a long time since I coded in C/C++, correct me if I'm wrong.
In the C++ Working Paper, the Standardese is N4527 5.2.2 [expr.call]/4 "When a function is called, each parameter (8.3.5) shall be initialized (8.5, 12.8, 12.1) with its corresponding argument." followed by 8.5 [dcl.init] where /17.8 has a nice example:
"Note: An expression of type "cv1 T" can initialize an object of type "cv2 T" independently of the cv-qualifiers cv1 and cv2. int a; const int b = a; int c = b;"
(A "cv-qualifier" is const or volatile; they behave similarly in many contexts, hence the collective terminology for convenience.)
Assuming you do it from the start you shouldn't be running in to issues.
But from my experience const really helps in enforcing directed graph constraints on logic and data flow which is great - and forces code to be easier to reason about.
What's more, it can "infect" any other code that touches it, in turn requiring casting or weird const additions. For a simple rant, check the first few paragraphs here:
You can't just declare something a constant then cast it out of being constant later? Of course not, that's the entire point!
Or any of several other things, including some truly odd possibilities using operator and function overloading.
It's just really gross as implemented.
And this is 'const''s fault? Others have asked - can you provide an example? Because what you're describing sounds like it's going to be a hot mess, const or no const.
I don't really think that const can make bad data representation any worse than it already is.
std::vector<char*> vec1(...);
std::vector<char const*> vec2(...);
std::copy(vec1.begin(), vec1.end(), vec2.begin()); // works (1)
std::copy(vec2.begin(), vec2.end(), vec1.begin()); // fails (2)
If this compiled, the second example would invoke undefined behavior and crash if you attempted to write to an element that pointed to read-only memory (e.g., a string literal).Container element types are almost never declared const unless their values are truly immutable (in this case the pointer is mutable, but the underlying memory is not, which is kind of strange).
...
std::vector<char*> vec1(...);
foo(vec1); // fails
The idea is that you should be allowed to pass non-const vector to const function who accepts same vector (const or not const) with const variables. Because the function foo is like saying "give me vector, I will not modify it's elements", and with current C++ standard this is not allowed.If you're writing a program, you should use the same type, std::vector<char* >, everywhere.
If you're writing a library, the API should avoid including container details in function parameters, and const and non-const T should be allowed.
template<typename Container>
void foo1(Container const& c);
template<typename Iterator>
void foo2(Iterator first);
template<typename Iterator>
void foo3(Iterator first, Iterator last);
template<typename Iterator>
void foo4(Iterator first, std::size_t num);
template<typename T>
void foo5(std::vector<T> const& vec);
void foo6(char const* const* first, std::size_t num);
foo6 is a pretty good alternative here, imo: std::vector<char*> vec(...);
foo6(&vec.front(), vec.size());
It would be cool if the compiler could figure templated type conversion out, but the crux of the issue is, as I understand it, that T<A> and T<A const> are not necessarily implemented the same way.Also note that std::vector<std::string const> won't compile -- it wouldn't be copy-assignable -- so these issues mostly seem to arise specifically when dealing with C strings and/or C compatibility. And reinterpret_cast is amazingly helpful for porting from C.
void blah(vector<const char * > &xs) {
xs.push_back("blah");
}
And you have the code calling it: vector<char * > xs;
blah(xs);
And that would not be valid. The rationale for the conversion rules for pointers to pointers is based on a similar sort of problem. (In fact I think it's exactly the same problem, at any level, but that only just occurred to me, so perhaps not.)(This isn't to say there aren't other similar sorts of conversion that would be safe, and that the language perhaps should support, just that this isn't one of the valid ones.)
I'm not even sure I understand the rant, though I've been a fan of const for years so it's probably automatic by now. So, say you have a class with a const member, and you have to pass it in to a function? Well, that's a bit unusual - const members are pretty rare in my experience. But the underlying problem sounds like: you have a const object, and you want to pass it into a function. There are three possibilities, I suppose:
1. The function takes const T & or const T * , because it doesn't change that argument. So no problem.
2. The function takes T, because it doesn't change that argument (and the object is small and/or the programmer doesn't care about the copying - irrelevant issues here). So no problem.
3. The function takes T & or T * , because it changes that argument. Compilation fails! But - no problem! Because this is what you want. You passed what's supposedly a const object into a function that expected to be able to change it. Obviously compilation has to fail.
So maybe the problem is people declaring parameters as T & or T * , when they meant const T & or const T * ?
I bet if C++ had const by default, this problem would never arise. We'd be striding, right now, arm in arm, towards our glorious const-filled destiny, singing a song about it as we went.
(References, by contrast, can never be reseated. So const T &const doesn't make sense. But... I didn't try this right now to double check, so if it works - though I think it doesn't - don't blame me. Or, if you do, at least allow me to plead extenuating circumstances, in the form of C++.)
'const' in C and C++ is broken. Not only is it like a Chinese word that varies wildly in meaning depending on where you use it and how it is inflected, but in C-family languages, 'const' modifies type, making objects that benefit from the compiler hints 'const' implies type-incompatible with objects that do not.
They're not type-incompatible in any way that doesn't violate those compiler hints in the first place - C and C++ fully support sensible coercion between const and non-const types. What more do you want?
This results in the problem of some know-it-all deciding that a core API needs to be const-correct, and checks in changes making it so, only to find that the entire fucking code base is now broken
Would you rather it compiled and silently broke? If some dingus breaks the build by changing an API without checking in the corresponding code using the API, how is that C/C++'s fault?
By contrast, languages such as Ada have explicit and implicit constantness restrictions ('in' parameters, for instance, cannot be modified), but these do not modify the type of the object, allowing objects with these restrictions to be type-compatible with objects without them.
This is bizarre. Can you take that 'in' parameter and then pass it on to another function as an 'in/out' parameter? If so, then either
1. Ada just made you a copy silently (bad)
2. Ada is broken and 'in' means nothing (worse).
If neither of those are true and an 'in' can't be transitively passed on as an 'in/out', then that's a type - however you want to refer to it, 'in' is then a type with all of the 'broken' type restrictions you're railing against in C/C++.
Suppose you want to write a function that returns all the nodes in a tree that satisfy some property. In C++ you have to define two different versions (or use templates to make the compiler clone it for you):
vector<Node*> GetGoodNodes(Node* root);
vector<const Node*> GetGoodNodes(const Node* root);
If more than one type is involved in the operation, there can be a combinatorial explosion.Does this function return data point that it creates itself, or does it retrieve these nodes from somewhere else? If it's the first option, who's in charge of deleting them? If it's a second option, who's in charge of counting references to these objects? Also, if it's a second option, and this data points are retrieved from a certain storage, are you absolutely sure that whoever gets this data should be able to modify them?
It returns pointers to the nodes in the tree. You know this because the caller isn't supposed to (or allowed to) delete them, because if the caller was responsible for that, they'd be wrapped in smart pointers.
> If it's a second option, who's in charge of counting references to these objects?
The pointers in the return value are valid as long as their particular descendant of the function parameter isn't deleted. There's no need for reference counts, and if they're present at all, they aren't updated by this function, since the caller isn't responsible for decrementing them. If the caller was responsible, the return value would be a vector of smart pointers.
> Also, if it's a second option, and this data points are retrieved from a certain storage, are you absolutely sure that whoever gets this data should be able to modify them?
If they have a const node *, they shouldn't. If they don't, they should.
And your final answer is, well, exactly the point I was trying to steer the conversation to. In other words, if you declare both methods, it means that you're doing something wrong: you're giving away data mutably and immutably at the same time, and it just doesn't make sense.
If you have a non-const node, you can access its children non-constly _or_ constly (which you could do anyway, since you could make your reference to the node be const at any time). If you have a const node, you can only access its children constly.
This makes perfect sense.
Yeah, come to think of it if there's a chance this function came from a pre-C++11 codebase I wouldn't be so sure that the caller wasn't supposed to delete those nodes. Well, it'd be an odd thing for this particular function, I guess, but it's something I've seen or might have done, in general. In that case maybe I'd have had the function take an output parameter instead, in order to give it a scary variable name.
Whenever I need to return a non-const reference/pointer to some inner data (which is not often) I name the method "mutableFoo()" to make clear what you're getting.
But most of the time (provided a sane API design) you won't have that problem anyway.
If 'in' is a type than so is the concept of an lvalue in C and C++ -- because constants (and 'in' parameters which function as constants with local scope) in Ada are not lvalues; they cannot be assigned to by the semantics of the language. (In C and C++, every sense of 'const' can be gotten around; even const methods can mutate object instance variables by const_casting this.) You can call any constraint imposed by the language a type and that may be true in the abstract sense, but what counts when talking about types in a language is what the language's own type system has to say on the matter.
Parameter modes (in, out, in out) form a part of the type of the subprogram (procedure or function) the parameter is a part of, but constantness is not a part of the type of the value in Ada. Note that this is not the only way types differ in Ada than in C++ when it comes to access: C++ types encode information about how a parameter is passed, Ada types do not. The Ada declaration:
procedure Foo(I : in Integer);
may be equivalent to C++ void foo(int i);
or void foo(const int& i);
depending on how the compiler chooses to pass the values. Note that even 'in out' parameters may be passed by value through a 'pass by copy-in copy-out' mechanism, so whether a parameter is passed by value or reference is largely handled behind the scenes, so the type system doesn't include that information. If it did, Ada would be even more verbose and unwieldy than its reputation suggests, as type enforcement is much more strict in Ada.What counts isn't so much what the specification calls a 'type' but rather how the compiler does type reduction - and with all of the compilers I've worked with, argument decorators like 'in' & 'out' are typically handled during type reduction.
depending on how the compiler chooses to pass the values. Note that even 'in out' parameters may be passed by value through a 'pass by copy-in copy-out' mechanism, so whether a parameter is passed by value or reference is largely handled behind the scenes, so the type system doesn't include that information. If it did, Ada would be even more verbose and unwieldy than its reputation suggests, as type enforcement is much more strict in Ada.
Very interesting! I know very little about Ada - other than that it has generally been considered as a safer alternative to C for applications-programming in the aerospace software world.
At the same time, I can say that without the ability to determine the how of how the compiler will pass arguments - by value, by copy, by reverse copy, by reference - I wouldn't consider Ada to be an alternative to C in the systems-programming domain, any more than I would consider Haskell or Javascript to be systems-programming languages. Your original claim, in your words, was that 'const' in C and C++ is broken. C, or any other systems-programming language, simply has more constraints on what const could possibly be. The Ada approach to handling const would simply never work in C with C's implementation guarantees. To pseudo-quote a great man, Dennis Ritchie,
If you want magical argument passing, sometimes resulting in silent and unexpected argument copying, you know where to get it.
It was fixed years ago. Now const behaves properly in modern C compilers.
I work on a large, mainly Java codebase. If we used `final` everywhere it is applicable, the code would have a ton of visual noise. We find that `final` is not as valuable in most cases.
`final` doesn't buy you as much in Java as `const` does in C, or (the lack of) `mut` in Rust.
In Rust, I can pass in an object reference without mut, and I know that the object will be the exact same after the function call.
In Java, even if a function takes `final ArrayList<..> xs`, the function can still delete every element of your array, modify elements at will, etc.
That means `final` is basically useless as far as using an API goes (I don't care if the implementation of a function reassigns to the same variable as the object I'm passing in, but I do care if they mutate it).
`final` only helps you avoid dumb reassignment bugs that tests should probably catch anyway, and in that case, if you are already writing good tests, why pollute the code with the visual noise of `final`?
(Obviously you know that, Steve, but it's the sort of thing that I find a lot of Java folks mentally don't get.)
As for "visual noise," I find it helps to highlight variable declarations and results in more readable code, but we're firmly in the personal opinion area here.
* Member variables of a class that should be initialized in the constructor
* Static final constants
* Any configuration-like variable
* Like you mentioned, when you need to initialize a variable in a particular scope, but the initialization could happen in several control paths of inner scopes
* Function parameters.
Re-assigning a variable used as a function parameter is frequently a mistake, and almost always makes code more confusing. I find the "this is defined only once in this function" philosophy to be useful in keeping complexity down in Java programs. Even if final doesn't make objects immutable.
Quite true.
> `final` only helps you avoid dumb reassignment bugs that tests should probably catch anyway ...
There is another reason for declaring parameters `final` in Java. It is the only way[1] which they can be used in an anonymous class definition such as:
return new SomeClass () {
override int addOne () {
return aNamedOuterMethodParam + 1;
}
}
1 - "The only way" as of Java 7. Newer versions may have removed this restriction, I haven't bothered to check.If you are working with code where you know that functions never modify their parameters then it is simpler to reason about what it does. I prefer to write code this way myself if there is no language mechanism to enforce it.
Other possible uses of `final` (to tag methods or classes) have the downside of reducing the flexibility of code by removing interfaces / "seams" that can be overridden. This reduction of flexibility will generally make things harder to test, which may or may not matter. Michael Feathers has a short article discussing this [1].
[1] Michael Feathers' "Testable Java" : http://www.objectmentor.com/resources/articles/TestableJava....
For built-ins, such as numeric types and "char" (String is obviously disallowed from this definition), final will prevent changes to instances of those types similar to what "const int" does for C++.
For pointers, however, the final keyword in Java limits the ability to change the variable holding the pointer, not to what it references. The equivalent in C++ would be:
SomeClass * const foo = &instanceOfSomeClass;
Here, the pointer "foo" could not change, but operations changing foo would be allowed.Java (the language) does not possess the semantics to express "the pointer given to a method cannot allow changes to what it points."
See http://duramecho.com/ComputerInformation/WhyHowCppConst.html
If I wrote a library and made something const, I did that for a reason. I didn't do that just to piss you off.
The most common usage of const in library code is for function parameters:
Iterator find(T const& value) const;
The const actually _helps_ callers: find accepts both const and non-const references. A non-const argument will be implicitly cast to const by the compiler.Compare that to:
Iterator find(T& value) const;
This function signature accepts only non-const references (unless T is const). A caller must const_cast a const argument to use it (bad).Return values are occasionally references:
T& front();
T const& front() const;
But "auto" makes the code shorter when the return type is const or unknown: auto& v = c.front(); // overload is deduced by the compiler based on the constness of c.1. I don't like the syntax for const [fair enough]
2. I want to have an object that obeys someone's const'ing of it but still be able to mess around with its internal state (like for logging, data caching, or intermediate calculations), and there's no way of doing that...
3. ...except that there is - it's the mutable keyword, but that's bad because other programmers other than me who are using it to modify 'const'ed classes (for logging, data caching, or intermediate calculations) can't be trusted.
Seriously - that's what he says:
One in particular annoys me because my programs often needed to be optimized for speed. [because I'm 'leet] This is that a method which is declared ‘const’ cannot even make changes to the hidden parts of its object that would not make any changes that would be apparent from the outside
...
In later versions of C++, the ‘mutable’ keyword was added which enables ‘const’ to be overridden for this purpose but it totally relies on trusting the programmer to only use it for that purpose so
!!!
http://www.ign.com/cheats/games/nba-jam-tournament-edition-g...
> someone figured out if you played the games in an odd, and very specific order, the flash memory would sort of work. So an extra leaflet was added to every box explaining how to use this "feature."
[0] http://www.gamasutra.com/view/news/249475/More_dirty_coding_...
I'm utterly horrified by the "(s)elf-exploitation" story. Ugh. Clever.
[1] http://www.gamasutra.com/view/feature/194772/dirty_game_deve...
[2] http://www.gamasutra.com/view/feature/132500/dirty_coding_tr...
I really don't like the idea of papering over the bugs so that your testers can't find them anymore, I'd much rather find the underlying issue. On the other hand, the described changes were often very practical, solved a customer need, and were manageable to implement. So that sounds like a win!
When Megaupload was destroyed, it was like losing the Library of Alexandria to me. The ATF can't just destroy the entire nation's set of self-storage units because they found drugs or pirate DVDs in some of the units. But that's what happened with Megaupload. Some people lost family photos, some people lost backups of theses... Why couldn't the authorities have taken it over, eliminated illegal content, and turned it over to some sort of Vichy CEO? There need to be protections for data storage customers in a FDIC sort of way, I feel.
https://web.archive.org/web/20140328003222/http://www.altdev...
Wow. Is this a horrible hack or a wonder of the tight integration between the GUI and the kernel in an operating system designed to be used in a desktop? I'm torn :)
AFAIK there is not even an operation like "put window into notification area" as part of WinAPI as the icons are mostly orthogonal to windows (and are not even intended to represent "more-minimized" windows).
The icon has to reference some window, but only because it generates window messages that have to be consumed somewhere. Fun fact: at least on 2k and XP, when the window goes away, the icon stays visible unless it is explicitly deleted by user code and is removed only when some message would be sent to now non-existent window (ie. when users mouses over the icon).