Organizing and managing code?
#1 Members - Reputation: 102
Posted 21 February 2012 - 09:06 PM
What's the proper way of organizing and managing my game code?
I ask because I keep seeing how even the tiniest memory leak could lead to disaster and with thousands of lines of code and dozens of files, debugging is going to be a nightmare.
First, what should I keep in mind so that debugging isn't a complete mess?
So far with all my little tests, the compiler seemed to have erratic behavior with errors. Syntax errors would get pointed to normally, but a null error would cause the game window to close. Is there an easy way to find and catch null references without having to check manually for them before using any variable? Any other debugging tips?
Second, memory issues
I basically just want a general outline of how to make sure the code structure is optimized and how to deal with memory leaks should they arise. I wouldn't want to be programming only to find out my entire code is an un-fixable mess after 4 months.
Like, any common pitfalls or an article showing the proper way to structure your code, or if anyone has any suggestions about stuff to look out for or bad practices, that'd be of great help!
Also, just to give you an idea of what I mean in case I'm not making much sense, I've only been seriously programming in Actionscript 3, where I never really bothered with removing everything when the game was done, I'd just stack what I'm going to re-use outside of the screen. But with C++, I'm the one who needs to remove *everything* when the user closes the window, so that's something I wasn't really aware of at the start.
#2 Senior Moderators - Reputation: 2448
Posted 21 February 2012 - 09:16 PM
Source control (like SVN, Git, Hg).
Properly named files.
Keep them relatively small and focused. A few thousand lines of code in a source file is OK, but not a great idea. For example, don't do this
Use Asserts, everywhere. They get removed during release builds, but can provide valuable checks for PROGRAMMER ERRORS in debug.
Additionally: DO not write code in asserts. An assert should be a boolean check, NOTHING MORE. Don't call functions and check their return result in an assert, because it'll be gone come release.
ScapeCode - Blog | SlimDX
#3 Members - Reputation: 102
Posted 21 February 2012 - 09:38 PM
Another question just popped into my head, about exiting and removing everything, do I have to keep track of everything on screen at all times in order to know what to remove? In case the user exits at the main menu, or at a specific level, where some assets will be there and others won't.
#4 Senior Moderators - Reputation: 2448
Posted 21 February 2012 - 09:44 PM
OmarShehata, on 21 February 2012 - 09:38 PM, said:
Quote
ScapeCode - Blog | SlimDX
#5 Members - Reputation: 102
Posted 21 February 2012 - 10:05 PM
Washu, on 21 February 2012 - 09:44 PM, said:
I was genuinely being grateful! I apologize if I came off as sarcastic.
Quote
That's actually a relief, so I don't need to remove every single variable and every single asset when closing.
Again, thanks for the speedy replies Washu! If you or anyone else still has any debugging and/or optimization "Gotcha's", feel free to post! Maybe others will benefit as well.
#6 Members - Reputation: 1217
Posted 21 February 2012 - 10:24 PM
For memory: use smart pointers. If you don't, you better have a dang good reason. And "speed" isn't a good enough reason unless you've proven with a profiler that it makes a significant, critical difference.
#7 Members - Reputation: 102
Posted 21 February 2012 - 11:27 PM
Also, another quick question. Right now, what I'm doing is putting the assets I'm using in the directory of all my source files, and in the bin/debug/ directory as well. But I imagine as the assets grow more and more, adding everything twice isn't the way everyone else does it. Or should I just keep having a copy of the assets in the bin/debug, and as well bin/release and my source directory?
#8 Members - Reputation: 1217
Posted 21 February 2012 - 11:50 PM
OmarShehata, on 21 February 2012 - 11:27 PM, said:
OmarShehata, on 21 February 2012 - 11:27 PM, said:
#9 Members - Reputation: 131
Posted 22 February 2012 - 01:18 AM
OmarShehata, on 21 February 2012 - 09:06 PM, said:
IMHO, debugging is a sign of failure. It's better to write correct code than to write buggy code.
Now that's easier said than done, and noone writes 100% bug-free code. But there are some ideas that are useful.
- Test Driven Development
For every piece of code, decide how you are going to test it. Design the tests *first* (could be a unit test, or some other kind of test). *Then* write the code, and rework it until the tests pass.
- Refactoring
There are two kinds of coding activities: refactoring existing code and adding new functionality. Only do one activity at a time. When refactoring, don't add new functionality, and test against existing tests. When adding new functionality, modify or design the tests first, then code (see above). Look for suitable refactorings both before and after adding new functionality.
- Profiling
Don't use the debugger as a profiler. Use proper profiling tools for tracking memory leaks and performance issues.
#10 Members - Reputation: 114
Posted 22 February 2012 - 02:53 AM
Felix Ungman, on 22 February 2012 - 01:18 AM, said:
Now that's easier said than done, and noone writes 100% bug-free code. But there are some ideas that are useful.
Felix Ungman, on 22 February 2012 - 01:18 AM, said:
Felix Ungman, on 22 February 2012 - 01:18 AM, said:
There are two kinds of coding activities: refactoring existing code and adding new functionality. Only do one activity at a time. When refactoring, don't add new functionality, and test against existing tests. When adding new functionality, modify or design the tests first, then code (see above). Look for suitable refactorings both before and after adding new functionality.
#11 Members - Reputation: 151
Posted 22 February 2012 - 01:06 PM
Debugging:
If your applications are closing on errors, you're probably running under release or have debugging disabled for some reason. When an error occurs under debug in MSVC, it will set a breakpoint at the line that crashes. This may be in your code or (commonly) inside a function whose source you may not have access to. Simply find your stack and follow it up until you find familiar code, step through and make sure you don't have any bad values. There are a lot of different techniques for debugging, you really need to find a good book on it.
Memory Management:
I am actually not a fan of "smart pointers", I feel like too much reliance on them signifies a weak architecture. However, they are still a common and useful utility. It's a good idea to get familiar with them. You should also look into memory pools and resource management. There are a lot of ways to manage memory, the most correct method depends on your needs for that specific project. You absolutely do need to use some sort of memory profiling. There are many free solutions. At the very least, you should check out this MSDN topic for simple leak detection.
#12 Members - Reputation: 579
Posted 23 February 2012 - 01:17 PM
I'm pretty good with the debugger, but not good enough to read x86 assembly. Here are some tips for you:
-Keep your code clean, simple and readable. Whitespace is good for preventing eye strain and information overload.
-Every time you use a 'new' keyword to allocate memory, automatically create a 'delete' keyword to deallocate it and make sure that the delete is reached.
-Be consistent with your naming conventions.
-Comment your code. It's for you. When your eyes are bleary, your brain is mush, or six months from now when you have to relearn your code, you'll be happier to read comments than code to figure out what you were thinking.
-floating point precision errors are a bitch to find and debug. If you're comparing floats for equivalence without a tolerance threshold, you're doing it wrong.
-If you're creating a mathematically oriented function and you know the range of valid inputs, throw it into a for loop and iterate through each valid value. Did you get divide by zero errors? did you handle negative values properly? if you're using floats, was your step size small enough to test for floating point precision errors?
-Sometimes it helps to create classes in a seperate project so that you can test and develop them in isolation. Once you know that your class and its member functions are rock solid, you can copy them into your main project and have a higher degree of confidence that they'll work without problems. This also helps to keep your classes loosely coupled.
-Aside from the debugger, my second most useful tool for debugging is pen and paper. Draw out the conceptual idea you're trying to implement and then synchronize the steps with the debugger (example: inserting nodes into a linked list at the front, tail and middle).
-The best bugs are the ones you don't have. A little bit of planning before code writing will go a LONG ways. There are three types of errors: Syntax errors, program errors, and design errors. Syntax errors prevent you from compiling. Program errors are stuff like referencing a null pointer or dividing by zero. Design errors are when you code a program with a broken design and you don't realize it until hours/days/weeks/months later. Planning and diagraming helps avoid them and helps keep your code organized.
-Lastly, stay disciplined. It's easy to get lazy and code something up really quick, but I've found that taking a little bit of extra effort to do things right the first time saves more time and effort since I don't have to refactor what I did out of laziness.
Good luck!
Hobby: Game Developer
Currently employed as: Sr. Sharepoint Developer in Afghanistan
Former Marine, two tours in Iraq
#13 Members - Reputation: 108
Posted 24 February 2012 - 06:00 AM
slayemin, on 23 February 2012 - 01:17 PM, said:
A *a = new A; //use a delete a;is very like not exception safe. Better:
std::unique_ptr< A > a( new A() ); //use a //std::unique_ptr deletes aYou don't leak memory if you use modern c++.
#14 Members - Reputation: 102
Posted 25 February 2012 - 09:58 PM
I completely wasn't aware garbage collection was *not* automatic in C++.
But wait, if I do use:
int b = 0;
and then use b all I want. Do I have to worry about garbage collecting such variables? Or do those get garbage collected automatically?
#15 Members - Reputation: 1217
Posted 25 February 2012 - 10:03 PM
OmarShehata, on 25 February 2012 - 09:58 PM, said:
I completely wasn't aware garbage collection was *not* automatic in C++.
But wait, if I do use:
int b = 0;
and then use b all I want. Do I have to worry about garbage collecting such variables? Or do those get garbage collected automatically?
int* b = new int; // Use b all you want, but be sure to delete it when you're done, or else memory will get leaked! delete b;
#16 Members - Reputation: 618
Posted 26 February 2012 - 05:48 AM
Memory Management and general safety procedures.
Organizational guidelines including file naming etc.
You can ignore the naming conventions section in the second link if that is not your bag. Everyone has his or her own style.
The part following that (Structure) is generally more widely accepted as good practice, and I made an effort to explain why each guideline exists rather than just to spew them out.
L. Spiro
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums
#17 Members - Reputation: 102
Posted 27 February 2012 - 09:52 AM
YogurtEmperor, on 26 February 2012 - 05:48 AM, said:
Memory Management and general safety procedures.
Organizational guidelines including file naming etc.
You can ignore the naming conventions section in the second link if that is not your bag. Everyone has his or her own style.
The part following that (Structure) is generally more widely accepted as good practice, and I made an effort to explain why each guideline exists rather than just to spew them out.
L. Spiro
Wow, thanks for those articles YogurtEmperor! I will definitely keep these as references.
One question about smart pointers though. I understand that anything you allocate with "new", you need to delete later on. And smart pointers automatically get garbage collected. But what if I declare a pointer like so:
b2Body * body;
Where I'm never actually using the "new" keyword, would this pointer be garbage collected like a normal variable or would I need to delete it or what?
#18 Members - Reputation: 102
Posted 27 February 2012 - 11:54 AM
It may seem like a subtle distinction, but it's very different from garbage collection. The key difference is that garbage collection actually spends time to inspect large portions of the heap and builds lists of what's reachable and what's not. shared_ptr maintains an internal reference counting mechanism that is appropriately modified on construction, copy construction, assignment, and destruction of the instances of the shared_ptr.
As far as your question is concerned, the pointer you've declared:
b2Body * body;
Is a variable that lives on the stack which is a pointer to b2Body. There's nothing special going on here and currently you've not used shared_ptr. body isn't garbage collected, or reference counted, or anything fancy like that. It's just standard stack frame allocation and deallocation when it scopes in and out.
However, if you later on perform a:
body = new b2Body();
There are several things that can happen. One thing that could happen is that you forget to ever put a delete in there. What happens is as soon as the scope of the variable body has ended, the stack memory for that pointer is deallocated but the heap object you allocated through the new is not. You've just leaked memory.
You could also add in all of the necessary deletes. This can be anything from just deleting right at the end of the scope if you don't need the object after that (in which case, you should have probably just put the entire object on the stack in the first place) or following all the places where the object's lifetime is relevant and finding when it dies and placing deletes there.
But, notice there's no garbage collection or reference counting sorcery going on here. You're using old school, manual heap allocations, where you have to do everything yourself. If you had used a shared_ptr() from the beginning, you wouldn't have to worry about those things (as much...). shared_ptr and many of the other smart pointers aren't perfect (shared_ptr can still leak if you aren't careful), but they are extremely convenient if you can afford to use them.


















