GLESGAE:Managing Resources Overview

From Pandora Wiki
Jump to: navigation, search

Contents

Introduction

Now we have some basic graphics rendering going on, we need to look at managing the resources we're starting to assemble before we start on anything more complicated - such as rendering 3D models, processing some logic so they do something, loading in more textures, and so on...

Sadly, this is where things get complicated, as we're now out of the realms of simple demos and into what can literally make or break an engine.
Too many really good engines end up unusable as their resource management is poor or non-existant and relies too much on the programmer to handle everything when they may not be aware as to what is going on under the surface with their resources. Freeing something that you may be finished with, but something internal isn't, can cause massive problems.
We're also programming in C++ so we have no automatic garbage collection and therefore need to deal with everything ourselves.

Of course, the biggest resource to manage is that of Memory, which also happens to be the most complicated resource to deal with as there are many ways in which to make a complete mess. A lot of engines overload the defacto new and delete methods so they can ensure that everything goes through their own memory manager. Other engines provide a large Singleton Memory Manager which you ask for memory, and use the space it gives back in whatever manner you need.

Another resource which tends to be forgotten about is that of logic itself. How you manage your states is just as important as how you manage your memory, as making a mess of how your logic gets called can provide rather drastic performance bottlenecks. Threading is also not the silver bullet that can kill your demons either, as if your logic has not been separated properly, you end up with all manner of issues dealing with deadlocks, threads overwriting memory all over the place, synchronisation issues, and other hellish nightmares you never want to have. Therefore, proper management of logic and states makes things much easier as anything that's self contained can get thrown on a thread and processed in parallel.

Finally, there's the art of debugging. It's much better to code defensively and assert as often as you can, than be left dealing with bugs near the end of the project that you need to track down. As such, having some helper functions to test for asserts, break if something goes wrong, and log warnings at various levels can be your lifeline in ensuring your logic is sane and any issues can be caught during development, rather than by the end user!

The Resource Manager

The first thing we'll be looking at in this section will be The Resource Manager. This is our all important gateway into maintaining the potentially vast amount of data that we'll be pushing through the engine.

Our Resource Manager is going to perform two primary tasks - File IO and the actual runtime management. This requires some careful fiddling however, as we need to ensure our Tools use the same data headers as the game. However, as all of our Tools will be using the same engine as the game, we'll be fine. It is something to be aware of, however, should you want to write Tools outside of the engine. If you want to output files for your game that the Resource Manager will load up, you'll need to ensure the file formats are the same.

The Resource Manager itself will manage multiple Resource Banks. These banks will only manage one type of data; a perfect example of using templates to reuse code! While this might seem restrictive, it's incredibly handy and very optimised. For example, when we start dealing with Entities we'll be able to just set a pointer on the start of the Resource Bank for our Entity set, and just let it run through them all. They'll all be in contiguous memory, so the processor will be able to cache it much more efficiently and speed through them all. That is, of course, assuming that each Entity contains all the data it needs to process directly, and it doesn't require the processor to go fetching pointers to other data elsewhere.

Each Resource will be of a template class, which itself acts like a smart pointer. What is a smart pointer? Well, whereas you could just new or malloc a random pointer anywhere you like, you need to also delete or free it when you're finished. By using a smart pointer, the deletion is taken care of for you. This is especially important for Resources as you may not be aware of something else that is using the Resource at the same time - especially in a threaded environment - and if you go and delete it while something else is depending on it, you've got yourself a fun bug hunt!
So let's not have bug hunts, and use a smart pointer.

The State Manager

Then, we will be looking at The State Manager. Each major area of an application is usually defined as a specific state. For example, you may have an Intro State, a Menu State and a Game State. The Intro leads into the Menu, which leads into the Game. The Game then might jump back to the Menu and loop back and forth until something quits. By separating the logic into these groupings, it makes it much more obvious what is going on while coding.

We'll also be employing the use of a State Stack. This will allow us to push and pop States onto one another; such as pushing a Pause State onto our Game State when we hit pause, and popping it back off when we un-pause. This allows us to keep all the information for the Game State active - but not processed - while the Pause State is in effect.

As you may have worked out from the previous paragraph, each State will be managing it's own objects as well. This means when we pop a State from the stack, it should clean itself up and release all resources it may have taken to initialise itself and run.

The Memory Manager

We'll have a look at implementing a Memory Manager, and wire up the State and Resource Managers to use it.

Our Memory Manager is going to be of the basic heap variety, in that you will ask it for a heap of memory and you will be free to use that as you please. This can create some odd syntax in that you need to allocate some memory from your heap, and then allocate your object into the heap, but it allows us to give anything that needs some memory it's own little portion to do whatever it pleases. As most libraries have their own memory management system that you can overload, this idea works perfectly in that we just generate a large heap for it, and wire up it's allocation utils to use it and we keep it out the way of everything else.

The Debug and Logger System

As mentioned, we should always code as defensively as possible - checking everything we do to ensure we are working with valid data before acting upon it.

When we need more information as to what's going on, we need to log out to a file or console to see the internals as we don't always have the luxury of running in a debugger - and indeed when threads collide, it's not always apparent which thread caused the issue! So a means of logging information is necessary.

File Formats and Data Structures

Before we start loading things up, we need to actually start declaring structures that we can load. This invariably involves fixed structures for faster loading, or variable structures for flexibility but the requirement to parse and slow down the load times. There are additional pros and cons for each method, but that's effectively what it boils down to.

We won't be creating File Formats and Data Structures for everything under the Sun as that's rather dependent upon what the application is up to, but we will be generating some rules to ensure that we can load and manage them properly, as well as define them properly as and when we need them.

The Thread Manager

Oh yes, there will be threads.

Threading is a black art in that there are many places that it can go wrong - even with the simplest of code. However, threading also gives us the benefit of running code in parallel which generally gives us an instant speed boost.

We'll primarily be using mutexes in order to ensure our code locks and unlocks memory that is required, and we shall also discuss what kind of code we should thread, what code we should leave alone, and how to ensure we write code that's as thread safe as possible. This will all be combined into a Thread manager to deal with the operation and management of threads, ensuring they're all cleaned up on exit and providing utility functions for our mutex handling.

The Model Loader

Once the boring stuff is out of the way, we'll start having some fun again. We shall be creating a model loader that will manage our own model format.

Of course, to get to our model format, we'll need to write a tool to do so, and this will be where everything discussed previously should fall together.
We'll be writing our tool to deal with both the COLLADA format and the Alias|Wavefront Obj format, so we should have everything covered.

The Logic Loop

Finally, we'll combine all the knowledge we've learned into one big demo for the end of this section and discuss the main loop at the same time.

We'll be creating ourselves a quick and simple little Model Viewer, something rather important considering we'll have created our own Model Format, so will need our own tool to view our own stuff!

Conclusion

This will be a big section, and it covers some very scary bits and pieces, all to do with managing resources - be it data or code.
However, it puts us in good stead to discover the ways of which our logic can be processed, and how to manage our logic through the use of Entities and Scripting.

We'll also be able to generate file formats and data structures for anything we feel like, know how to load them and save them, and all sorts of fun stuff that forms the crux of our engine.

Personal tools
community