Lessons to be learned, (Complex Resource/Object Systems) LONG!!

Started by
11 comments, last by Heaven 18 years, 2 months ago
So, I'm a senior project student in high school, this is my first major project of over 3000 lines of code, and the first to be using OpenGL in a big way... Over the past 2 months I've been struggling with a topic some people tend to ignore until it is grimly apparent in their code... Managing resources, caching them, loading, unloading, resources... I believe I've finally formulated a plan in my head, of course not without the help of others. Tip #1, you don't get anywhere unless you think for yourself!!! Think of the goal, think of steps you need to take to accomplish that goal, devise classes and objects that perform those duties, structs and data types that are used by those classes to get the job done... In my case, the goal is to create a system where you can lookup an Object's resources with nothing but a single ID.. I was blocked with several issues, first off, how do we handle loading these resources, how do we give them Object ID's? How do we store the data once we've loaded it? How do we not create more than one of the same resource in memory? All of these are major problems you need to think about when creating a resource/object management system... Well, I'll try to answer these one by one, please remember that writing this post is mainly for my learning, the best way to check if your system is logical and should work in code format is by seeing if it makes sense when you write it out and read it! 1. Storing your data, where? With What? How? First off, Where do you store resources? In memory, yes. But lets talk about the data structures you will be using. Vectors? They're dynamic, you can do alot of nifty things with vectors, Arrays? Not dynamic, but you can easily create a pointer sizeof array A.. Problem is, you must have all of the same data type in a vector, in an array, in almost any storage container! You can't throw vertices array A which is size enourmous into vector A, and still try to put vertices array B, which would be size tiny, as well into vector A... Well, hm, this poses a problem... Well, we know that a vector can hold as many things as you want as long as they are the same size.. well, what do vertices Array A of size enourmous and vertices Array B of size tiny have in common? A pointer! As long as the pointer is of type vertices, you're all set! You can have vertices array pointer A and vertices array pointer B in the same vector! Sweet, so now you'll have a storage container that will work with any structure of vertices you want...
 std::vector <Vertex*> Vertices; 
So you're probably all asking by now, what the heck is a Vertex? Or Vertices? Well, for every polygon, be it a complex thousand polygon human figure, or a simple cube, needs vertice data! A vertex usually comes in the form of a Vector, a vector is essentially, three floats, three integers, whatever you decide, I'd suggest floats, they allow for more precise tracking :)..

struct VertexData
{
	float vertex[3];
};
Simple huh? The VertexData structure handles one point of a triangle, a float array of vertices, the size of 3. So say our polygon we want to draw has 300 vertices. We create a VertexData array += the number of vertices, then we create a pointer to this new array of vertexdata structures... Then we throw the pointer into our Vertices container, that nifty vector up there. Lets clean it up a little by throwing that vector into a nifty container class, we don't want global variables floating around..

class VertexContainer
{
   friend class ResourceCache;
   std::vector<VertexData*> Vertices;
}
O.o friend class? Oh gno's don't over oop us Shamino! Just relax there little whipper snapper! Think about what friending a class does, it's usage is simple, it just makes it so the designated class can access private member functions. It is used in this implementation as more of a design logic helper outer code type thingy :). Next up, Wrapping up the containers! Next post!
----------------------------------------------------------Rating me down will only make me stronger.----------------------------------------------------------
Advertisement
What is a group of containers? What is a bunch of stuff? It is a cache of containers, or a cache of stuff!

Thus the wrapper, ResourceCache.

This guys job is simple, somehow we need to manage our containers, at least on the lines of how things are placed in the container, and how they are removed.. Also to provide a logical center of what is stored in the cache? Think abstractly, do you need to know what is in a warehouse before you know what the warehouses function is? No, this is why all Resource Containers are tied up ina generic class, ResourceCache..

There is one thing that you do need to know before hand though, and we'll get to that, if you're going to place something in the warehouse, you need to know which warehouse to put it in, but that is for later!

Lets by this point in time assume we have more than one resource type, and a container for each of those types... What do most objects in your game have in common? They all have vertices, textures, tex coords, Physics data such as mass, and a position...

struct VertexData{};struct PositionData{};struct MaterialData // textures and stuff{};struct PhysicsData{};


I'll assume you know how to create a container class for each of these data structures based on my first post.. Again, all of these data structures are ultimately tied to a container which is tied to a cache.. The ResourceCache..

Think of the ResourceCache as a well oiled machine that takes resources you hand it, places them in the right spot, right container, all encapsulated in your warehouse...

Some code:
class ResourceCache{	PhysicsContainer   Physics;	VertexContainer    Vertices;	PositionContainer  Positions;	MaterialContainer  Materials;};


As you can see, ResourceCache ties together all the containers into a unified cache of resources, ResourceCache can access all the data containers, and ultimately all of the data in each individual structure, down to the last point on your sword object.

What can ResourceCache really do though? I say it is a type of automated warehouse, well, not fully automated, but we'll get to that... Resource cache will hold the responsibility to actually fill up the storage containers with whatever it is given, don't worry about where he gets it yet, that is for the next post.

We need functions to load data, and store it in the containers. Simple right? Yes, but it is beginning to get tricky, this is where you actually start devising methods of loading from files, loading from data given from somewhere, anywhere! Below is a list of options you might want to consider adding to your repetoire of things you might want to load..

class ResourceCache{        friend class ResourceManager	PhysicsContainer   Physics;	VertexContainer    Vertices;	PositionContainer  Positions;	MaterialContainer  Materials;public:        void LoadMS3DModel(std::string Filename){}        void LoadQuake3Model(std::string FileName){}        void LoadTexture(std::string Filename){}};


You should get the point, think about where you're getting your picture information from! Write a function to copy that data into the new data structures that our system will use, vertices go in vertex container, textures and material info go into material container. I'm not going to go into details about writing a loader function, as it is out of the scope of this thread, We're focusing on the management of resources once theyre loaded! :)

You also need functions that can return certain elements, I'll explain more about ID's later..

O.o, notice that new friend did you? Well, as I said before, ResourceCache isn't a fully automatized resource storage facility! Making it do so wouldn't be object oriented, the design could end up being clunky and unmanagable, we want to handle it in small pieces!

Next up: ResourceManager
----------------------------------------------------------Rating me down will only make me stronger.----------------------------------------------------------
Here comes the point in the thread where I actually begin theorizing rather than giving you provenly tested code, don't fret, I'm pretty sure I've got this down pat.

ResourceCache, as we said before, does not work on his own, he is like a paintbrush without an artist. ResourceManager is that artist.... What does ResourceManager need to do? Well first, lets think about what ResourceCache can't do.

He doesn't know what to load.
He'll load whatever you give him regardless if it is already in the warehouse or not
He doesn't decide when to clear out his caches.
He doesn't decide when to start adding to his caches
He doesn't have a way to keep track of what resources are there.

ResourceManager will do a few main things, keep track of what resources have been loaded, take orders on what needs to be loaded (from where you don't need to worry about yet), and he needs to give orders to clean up shop, as well as needing to give orders of what to load when, for how long...

Another thing to think about, is what kinds of caches will we have? Even though they might contain relatively the same information, do you really need the mass of Object A when he is either out of your sight completely, or so far away you really just don't need his information, no, not at all. Do you need information at all times for an object that has a lifetime of mere seconds? Think bullets, rockets, projectiles, particles, things like this!

We'll continue with some code:
class ResourceManager{Resourcecache LevelCache*;ResourceCache TempCache*;ResourceCache PermanentCache*void Create_IDs(Object);{    Read what resources the Object needs (comes in the form of strings for filenames, or wherever you're getting the info from.)        If resources exist, do not load them! Instead return the index of the container theyre in <-- THIS IS YOUR OBJECT ID!!! PRECIOUS!!!    If they don't exist, tell the caches to load them, and then return the index of the container theyre in <---- your OBJECT IDs!!!    Set ID values in Object's info structure (explained shortly) equal to what this function is popping out...}};


Okay, whew, that was alot, I'll try to explain this the best I can. If I can't explain this, I'll need to rework my system until I can explain it.

Alright, so lets start at the top, ResourceManager has a couple resourceCache types... He can handle Level Cache, the kind of resource you need throughout your actual game level, or anything for an extended but non permanent length of time. He can handle temporary cache, things like particles, bullets, laser guns, rockets, little things with a short life. And then finally he can handle a permanent cache, things that you need throughout the entire program life, menu systems, things like that.

This guy is like a director of sorts, he just directs traffic in and out of the caches... He needs a method to hand out Object ID's. I'll take this time to explain an ID, most of this will be ripped from another source.

"An ID is simply the index of the object in the index which contains all of the game objects, or at least all of them for the current level, or the current map area that has been loaded in (say in a flight sim)."

This boils down to, the object ID is the element where the resource is held in in our container! This I feel is the best way to keep track of ID's as well as generate them, and almost the only way to tie resources and ID's together. We need ID's to provide quick and easy access to information that certain systems might need, like your Rendering engine, your physics and collision engine, your sound engine, multiple engines in your system can use a simple list of ObjectID's to deal with..

There are still a few questions to answer, What exactly is Object giving to the Create_IDs function to let it do its job?

The answer lies in the next post.
----------------------------------------------------------Rating me down will only make me stronger.----------------------------------------------------------
And now on to part 4 of the system.. We started with data structures, which were contained in containers, which are held together by a resource cache, which is driven by resource manager. But still, The ResourceManager himself needs to be given orders, all he does is process orders, he doesn't give them.

Now comes in your Object class. What is an object? An object can be a plethora of things, ranging from a player, a space ship, a carrot, a box, a sword, a monster, a house, and to go deeper, you can have rotten carrots, you can have big boxes, and little boxes, a sharp sword or a dull sword, a ranch or a farm. But, all in all, ultimately, they are Objects, and they all have similar information that needs to be filled out. Lets start with some code.

struct ObjectInfo{	DWORD dwID;	DWORD *dwTextureIDs;        DWORD *VertexIDs;        DWORD *PhysicsIDs;};class Object{public:        	ObjectInfo ObjectInformation;        std::string "Model.ms3d";        std::string "Texture.bmp";        std::string "other resources"};


Inheritence will play a big part in this part of the system. We can't assume that the ID's for all objects will be the same, that would be silly! Object is the base class for ALL objects in the system that are to be drawn. You will also notice that each object will know what resources it has, the only thing it doesn't know is a simple ID to tell a renderer, or a physics engine, or a sound engine, what to do. We don't want to lag down major engines of the game with functions that handle filenames, that simply isn't their job.

But, who does want to handle filenames? Well, ResourceCaches job entirely is to handle filenames, imagine that. But, first off, you gotta remember that he just blindly loads things, this is where ResourceManager comes into play.

ResourceManager will recieve an object, read its filenames and wherever you get data for that object, it checks to see if those filenames have been loaded or not, if not, he sends the command to resourcecache to load the files, and return the ID. If the objects were found in his list of loaded things, (a linked list), he already has the location of that resource (the index ID), written on his massively cool list of registered objects.
With the given information ResourceManager fills out Object A's information regarding ID's...

Now your Object, be it a carrot, a squishy carrot, a sword, a dagger, a crate, a small crate even, can act on its own will, and tell all the engines what the dilio is.

And that is where my story ends, please ask questions, challenge my integrity as much as you possibly can, because I'm learning too.


[Edited by - Shamino on January 19, 2006 10:32:09 AM]
----------------------------------------------------------Rating me down will only make me stronger.----------------------------------------------------------
Alright. I don't understand exactly what you are trying to accomplish with this system. To meet your originally-stated goal being able to look up a resource with nothing but that resource's unique ID, you can write much less code, resulting in a much cleaner, efficient system.

Throughout the discussion your aim seems to shift. Initially you are talking about managing vertices and textures, later you are talking about managing renderable objects. I get the feeling you are trying to design a supergeneric system for managing any kind of data imaginable, but this a bad idea. Different kinds of data have different usage patterns; you shouldn't try to have everything managed exactly the same. For the management of drawable objects, for example, you generally will want some means of culling away objects that aren't visible. But that kind of culling makes no sense to perform directly on a texture, and even less sense to perform on a single vertex. Vertices should not be managed in system memory anyway.

I have some thoughts and critiques on your system but you need to clear up one big point for me first: Is your system designed to things like vertex data, textures, sounds, et cetera, seperately, or does it only manage renderable/client-side "objects" that happen to use all of those things? Initially, your discussion seemed to imply the former (i.e., you were talking about how you were going to track and manage vertices), but as I continued to read, it seemed like your focus was shifting to the management of only larger "objects" that contained vertex and texture data, et cetera. So which is it, because I don't want to point out flaws if the system isn't even intended to handle those flaws anyway.
Also, it seems like the deliniation between 'game object' and 'object representation' is quite blurred.

Quote:
What do most objects in your game have in common? They all have vertices, textures, tex coords, Physics data such as mass, and a position...


No [imo]. Game objects have no vertices, no textures, and rarely physics data.
Well my logic is why seperate the two when they are pretty much one in the same..

A sword has a model, it is a game object.. why not combine the two?

What game objects don't have something to look at?

To answer the first question, the aim is to manage resources for renderable objects, all renderable objects have vertices, textures, physics such as mass, and sound data...

----------------------------------------------------------Rating me down will only make me stronger.----------------------------------------------------------
Because its highly common to need to seperate the two. An AI doesn't care what the sword's model is. A server doesn't care what the sword's model is.

It's rather trivial to mix two smaller classes, quite difficult to split a larger.
I see, so you're saying create two types of objects? Logical game objects, I.E a monster and all the stuff regarding that monster, and a renderable object, monster, which you kind of link to the logical object?
----------------------------------------------------------Rating me down will only make me stronger.----------------------------------------------------------
Also, games generally thrive off of invisible triggers and magic objects of that sort that deal with things behind the scenes. Then there's temporary effects like particles, glows, heat haze, etc which may not have a real object connection to them.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

This topic is closed to new replies.

Advertisement