Jump to content

  • Log In with Google      Sign In   
  • Create Account

Interested in a FREE copy of HTML5 game maker Construct 2?

We'll be giving away three Personal Edition licences in next Tuesday's GDNet Direct email newsletter!

Sign up from the right-hand sidebar on our homepage and read Tuesday's newsletter for details!


We're also offering banner ads on our site from just $5! 1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Organizing and managing code?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
17 replies to this topic

#1 OmarShehata   Members   -  Reputation: 205

Like
0Likes
Like

Posted 21 February 2012 - 09:06 PM

Okay so I've been delving into C++ for a couple of months now, trying out everything, using SDL, openGL, box2D etc.. and I think I'm ready to start on the project I've had in mind, but there's one thing I really fear before starting.

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.

Sponsor:

#2 Washu   Senior Moderators   -  Reputation: 5358

Like
1Likes
Like

Posted 21 February 2012 - 09:16 PM

Folders.
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.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.
ScapeCode - Blog | SlimDX


#3 OmarShehata   Members   -  Reputation: 205

Like
0Likes
Like

Posted 21 February 2012 - 09:38 PM

Wow, asserts, that's a really valuable tool there, thank you Washu!

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 Washu   Senior Moderators   -  Reputation: 5358

Like
1Likes
Like

Posted 21 February 2012 - 09:44 PM

Wow, asserts, that's a really valuable tool there, thank you Washu!

Can't tell if you're being sarcastic here or not. But yes they are very valuable. An "assert(this);" can save you hours of debugging by simply pointing out that "this was null". An assert is not for USER ERROR, it is for the things that you are assuming are true. I.e. if you have a function that takes a string and the string must never be null... assert(string != null); can save you from going "FUUUUU". Especially since it tends to give you a nice stack trace, so you can find exactly where it's being called.

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.

Uh, asset tracking is rarely associated with the visibility of the asset, just its existence. When exiting your application you should attempt to do as little work as you have to, preferring just to clean up what needs to be cleaned up (i.e. resetting the resolution to default, maybe closing sockets, and saving any open files), and then just exit. Windows will tend to handle the rest.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.
ScapeCode - Blog | SlimDX


#5 OmarShehata   Members   -  Reputation: 205

Like
1Likes
Like

Posted 21 February 2012 - 10:05 PM

Can't tell if you're being sarcastic here or not. But yes they are very valuable. An "assert(this);" can save you hours of debugging by simply pointing out that "this was null". An assert is not for USER ERROR, it is for the things that you are assuming are true. I.e. if you have a function that takes a string and the string must never be null... assert(string != null); can save you from going "FUUUUU". Especially since it tends to give you a nice stack trace, so you can find exactly where it's being called.


I was genuinely being grateful! I apologize if I came off as sarcastic.

Uh, asset tracking is rarely associated with the visibility of the asset, just its existence. When exiting your application you should attempt to do as little work as you have to, preferring just to clean up what needs to be cleaned up (i.e. resetting the resolution to default, maybe closing sockets, and saving any open files), and then just exit. Windows will tend to handle the rest.


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 Cornstalks   Crossbones+   -  Reputation: 6991

Like
1Likes
Like

Posted 21 February 2012 - 10:24 PM

Everything Washu said. Expanding on source control: test your code before you commit. Don't commit anything that you know will break something else without having fixed that something else. Give useful, concise messages when you commit, so that 3 months down the road when you have to look back to why you made that change that introduced a bug, you have a good idea. Try to keep your commits to small units (i.e. commit things in groups, where each group makes logical sense; don't change a bunch of files and commit them all together if the changes in one file were completely unrelated to changes in another file).

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.
[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#7 OmarShehata   Members   -  Reputation: 205

Like
0Likes
Like

Posted 21 February 2012 - 11:27 PM

Wow..smart pointers. Is it just me or is C++ a very dense language? There are so many ways to do everything and so many things to take advantage of. And I thought having "pointers" was strange, as opposed to having everything just being a normal variable. What's wrong with using just regular variables anyways?

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 Cornstalks   Crossbones+   -  Reputation: 6991

Like
1Likes
Like

Posted 21 February 2012 - 11:50 PM

Wow..smart pointers. Is it just me or is C++ a very dense language? There are so many ways to do everything and so many things to take advantage of. And I thought having "pointers" was strange, as opposed to having everything just being a normal variable. What's wrong with using just regular variables anyways?

Nothing is wrong with regular variables. There are just certain things you can't do with them. For example, say you want an array of integers somewhere, and you want to prompt the user how big this array should be. You can't just create a normal variable on the stack that has an arbitrary length that you can change depending on what the user says. You have to prompt the user how big the array should be, and then dynamically allocate that memory on the heap and access it through a pointer, which is exactly what std::vector does for you.

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?

Why are you putting them in multiple places? So that when you run the program they're in the same folder as your executable so it can find the resources? If so, you should change your IDEs settings so that the directory used when debugging is the directory in which your art assets are placed, and keep them in one place.
[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#9 Felix Ungman   Members   -  Reputation: 1049

Like
1Likes
Like

Posted 22 February 2012 - 01:18 AM

First, what should I keep in mind so that debugging isn't a complete mess?


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.

openwar  - the real-time tactical war-game platform


#10 doeme   Members   -  Reputation: 712

Like
1Likes
Like

Posted 22 February 2012 - 02:53 AM

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.

I would not say that debugging (as in "using a debugger to find your bugs") is a sign of failure as such, but finding a bug without a clue where it originated with a debugger can be a real pain. And while nobody writes 100% bug-free code a good way to minimize bugs is to minimize your code, the less code the less bugs. Avoid code duplication under almost every circumstances except maybe for the simplest stuff. Try to find common patterns in your code and put it in a common place and don't reinvent the wheel all the time.

(could be a unit test, or some other kind of test).

Maybe have a look at Design by Contract (http://en.wikipedia....ign_by_contract) as an extension to simple Asserts to check for code correctness. But don't forget to compile the contracts out before you ship. Posted Image

- 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.

I totally agree on that one and I like to add: Do not be afraid of refactoring. While it may be tedious to do it will lead to a cleaner code base and if it doesn't you're doing it wrong.

#11 SeiryuEnder   Members   -  Reputation: 199

Like
1Likes
Like

Posted 22 February 2012 - 01:06 PM

I'm not sure what IDE you're using, I'm going to assume MS Visual Studio since it's arguably the most commonly used.


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 slayemin   Members   -  Reputation: 2786

Like
1Likes
Like

Posted 23 February 2012 - 01:17 PM

:((( I write some code and then immediately step through it line by line with the debugger to make sure that it's working as I expected. Occasionally, I find errors or mistakes I wasn't expecting.

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!

Eric Nevala

Indie Developer | Dev blog


#13 GorbGorb   Members   -  Reputation: 112

Like
1Likes
Like

Posted 24 February 2012 - 06:00 AM

-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.

Don't use new unless you need a custom data structure or for unique_ptr. Use std::make_shared and std::vector instead (you could also create std::make_unique, I'm sure it'll make it into the standard soon).
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 a
You don't leak memory if you use modern c++.

#14 OmarShehata   Members   -  Reputation: 205

Like
0Likes
Like

Posted 25 February 2012 - 09:58 PM

This thread is really becoming a very valuable reference for me (and hopefully others too!)

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 Cornstalks   Crossbones+   -  Reputation: 6991

Like
1Likes
Like

Posted 25 February 2012 - 10:03 PM

This thread is really becoming a very valuable reference for me (and hopefully others too!)

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?

You only have to worry about memory in C++ when it is allocated via new (or malloc, if you're working with C). If you don't create the variable with new, you don't have to delete it. So yes, you can use b all you want and not worry about cleaning up. However, if you did the following, you would have to worry about cleaning up:

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;

[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#16 L. Spiro   Crossbones+   -  Reputation: 13949

Like
1Likes
Like

Posted 26 February 2012 - 05:48 AM

You may find my articles of use.
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
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#17 OmarShehata   Members   -  Reputation: 205

Like
0Likes
Like

Posted 27 February 2012 - 09:52 AM

You may find my articles of use.
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 snowmanZOMG   Members   -  Reputation: 889

Like
1Likes
Like

Posted 27 February 2012 - 11:54 AM

Smart pointers aren't garbage collected. The different variants of smart pointers do different things to keep track of when something should be deleted, but garbage collection isn't what's going on here. Garbage collection is a process that inspects the heap and searches for reachable memory objects and ones that aren't and deletes the unreachable ones. Smart pointers, such as shared_ptr use something called reference counting which keeps track of how many things currently have access to the pointer. When the reference count reaches zero, the shared_ptr knows that nothing can access the pointer and deletes it.

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.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS