Graphics engine structure + resource manager questions

Started by
19 comments, last by Valor Knight 18 years ago
Quote:Original post by Promit
These concerns led me to finally build a handle based system. Each handle is a black box wrapper that internally contains an integer index into the internal array of resources. The rendering core object provides a number of managers. Clients request these managers to get some resource handle by string, and a handle is returned immediately (the actual resource is scheduled for loading in the background if necessary, and does not stall the function). Then all of the rendering functions and structures take handles (which are intended to be value types, they're passed by value in most cases). The managers internally allow the handles to be resolved to actual pointers, but external clients cannot retrieve handles. Resolving a handle to a pointer is a straightforward array access and is basically free. The manager keeps a map/hashtable of string->array index which are used to look up string names and build handles when requested.

I like it, I wanted to do streaming later on, and doing it this way to start would be all the better. A couple of things though, say you wanted a to load a texture image, you would call the manager to make an image, and the return a unique handle to that image. Now, if another resource wants to load the same texture, it looks up the texture name string (its filename, right?) if it exists, return the handle (which is an int or long), if not create one? Now say I wanted to use the texture, how do you get it out seeing you only have a handle to the resource? would the resource manager include functioniality to render/manipulate as well?



Quote:Original post by leiavoia
1) use a universal interface. You could go with operations like this:

TYPE* GetResource<TYPE>("name");

Then, though some kind of magic, you could select the right resource from the right list of resources.

Yea, that is what I think will have to work

Quote:Original post by leiavoia
- you asked how you actually would store the resources internally. Most usually use a map, like so:
map< string, Resource* >;

What is the diffrence between a has_map and a map? I have seen in them both being used in a resource manager at the same time


Quote:Original post by Telastyn
Just a slight caveat before I begin, Resource Managers are one of those things that you'll design differently as you gain experience. Maybe don't mind so much if yours isn't totally swanky to start out with. It'll get better. The important thing is that it works, so you can get on with coding the game [and learning how your resource manager rocks/sucks]...

That is like my whole project so far, I dare not enter the gui, or the particle system code anymore, I will just delete it and start over
There is no life without honor
Advertisement
Quote:Original post by Valor Knight
Quote:Original post by Promit
These concerns led me to finally build a handle based system. Each handle is a black box wrapper that internally contains an integer index into the internal array of resources. The rendering core object provides a number of managers. Clients request these managers to get some resource handle by string, and a handle is returned immediately (the actual resource is scheduled for loading in the background if necessary, and does not stall the function). Then all of the rendering functions and structures take handles (which are intended to be value types, they're passed by value in most cases). The managers internally allow the handles to be resolved to actual pointers, but external clients cannot retrieve handles. Resolving a handle to a pointer is a straightforward array access and is basically free. The manager keeps a map/hashtable of string->array index which are used to look up string names and build handles when requested.

I like it, I wanted to do streaming later on, and doing it this way to start would be all the better. A couple of things though, say you wanted a to load a texture image, you would call the manager to make an image, and the return a unique handle to that image. Now, if another resource wants to load the same texture, it looks up the texture name string (its filename, right?) if it exists, return the handle (which is an int or long), if not create one? Now say I wanted to use the texture, how do you get it out seeing you only have a handle to the resource? would the resource manager include functioniality to render/manipulate as well?
It first checks a string lookup, and if it finds it a new handle is returned and the resource ref count is incremented (handles are value types remember). As for using the texture, there's a very clear division between internal renderer and external clients, enforced by a DLL boundary. You can only tell the renderer your intentions. Roughly speaking, you will tell it to queue a rendering command of the form (heavily simplified):
struct RenderCommand{    Handle<Model> Model;    Matrix Transform;    Handle<Texture> Texture;};
So you package and send one of those structs off to the renderer interface, and it knows how to get a Model* and a Texture* and use the relevant objects correctly. You are never granted an actual pointer, and for that matter you don't even know what the objects look like. The external header only forward declares them, which allows you to build templates of a certain form but doesn't actually grant you any knowledge about them. (Technically you're not even entitled to know if the resource is in memory or not, but I do allow you to query.)
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
ok, then the renderer should be able to get more than the handle out of the resource manager? such as a pointer to the texture, if the texture is available?
And I assume you have a resource manager for each type of resource, texture, model, ect.. derived from a base resource manager, and then a manager that manages what resource types get loaded into what - so you dont need to know all the resource manager avail?
There is no life without honor
Quote:Now say I wanted to use the texture, how do you get it out seeing you only have a handle to the resource? would the resource manager include functioniality to render/manipulate as well?


Your resource manager just manages resources. The resources themselves ought to know how to do their job. Example: My 2D engine needs OpenGL Textures to work. OpenGL references it's textures via a simple integer. That's fairly benign (you can't accidently delete an int), however the drawing code to actually draw an image on the screen is the same regardless of what image i'm drawing. So the resource manager hands out a special homebrewed "Texture" class instead of OpenGL integers and drawing them by hand. This gives me an interface like this:

Texture::Blit(params);

The clients that need to draw a texture are completely isolated from the underlying drawing code. In fact, the client would have no idea even what library was being used. It could be OpenGL, SDL, DirectDraw, or anything else.

Because the drawing code and the clients that draw are separated by encapsulating drawing in a class ("Texture"), you can change the implementation of how you draw with no changes to client code. This is ideally how you should treat all of your resources. Don't hand out raw pointers or anything that could directly manipulate the system. Just hand out little stubs that perform functions. The stubs know how to do their job. It's not the client's business.

Texture::Blit()
Sound::Play()
Model::Render()
Font::DrawText("hello world")
. . . etc . . .
well, yea it will have to be in another class. I was just commenting on his method where it only gives out a handle to the resource. How does one access the data if I only have a handle, I would have to get a pointer to the data or use the data somehow.

in renderer
DrawTexture( resourcemanager.getTexture("handle") ); ???
then you would need to perhaps make the renderer a freind class so that the data can be hidden for the user, but still able to be extracted for the rendering process?
There is no life without honor
Yeah, the prefered option would be something like this:

Texture* t = Manager->GetTexture("laser");

t.Blit(x,y); // draws image.

So the manager might give out a pointer to something, but since it's encapsulated by your own Texture class, it's not something you can directly manipulate.

This is opposed to something less secure like handing out raw pointers:

SDL_Surface* surf1 = Manager->GetTexture("laser");

// rogue code!
delete surf1;

// (Manager still thinks the pointer it gave out is still good, so it gives it out again if asked) :

// ask for the same texture
SDL_Surface* surf2 = Manager->GetTexture("laser");

// do something with it:
SomeFunction( surf2 );

// *** segfaults ***
I do something a little like what Prozak and Telastyn in that I use a resource definition, except it's a little different. (Going to try and space it somewhat to make reading easier)

I like mine because it took way too long to program, and I'm somewhat proud of it. The main downfall that comes with it is the fact that it isn't to secure in the fact that hve things register in the source files themselves (a.k.a, they are registered in a global initialization). Is that bad practice? Yeah, I think so, but I don't want to have to go to a new source file to register everything every time I add something new to be registered. All in all, it would probably be best to register them all in a function, but I'm kind-of lazy right now...

To start with, I have two base classes: 'RDEF_BASE' and 'CResource'. CResource is used for the actual resources themselves, like the sprites, fonts, sounds, etc., and RDEF_BASE is for the loading format, and contains data such as the resource's name, a pointer to the resource that it is connected (will be explained later).

With the resourece base (CResource), I have a virtual Load() and Unload() method. CResrouce::Load(...) takes one argument, a pointer to the resource-definition base (RDEF_BASE*), and CResource::Unload() has no arguments, since it, well, unloads. The resource also has a virtual type function, that returns, well, its type (to be compared with the base).

In the resource definition base (RDEF_BASE, r.def. base for short), it has information such as the file name where the resource is stored (or, if its a virtual file, the virtual file's name), and it also has a pointer to the resource that is registered to it (ex. 'resource_register("sndKaboom",&sndKaboom)'). With that pointer, it can control when the resource itself loads, unloads, or even reloads. It is also very handy when you plan on loading only certain parts of your resources. For that reason, I have 'load points' that can be defined in each r.def. base. Whenever you're loading a certain level with certain graphics and such, you would simply have to define the load point with a name in the resource definition file (later explained), and in the code somewhere, you would load/unload those points (ex. 'resource_load_point("jungle")' and later 'resource_unload_point("jungle")'). There is also a default load point, which would be 'resource_[load/unload]_point(-1)'.

I could go into greater detail, but I bet I have already exceeded what I should have typed.
But to finally tie it all together, I have my resource definition type (not base), called 'RDEF_TYPE'. This has a function pointer to create a child of RDEF_BASE, and it has a name, such as "SPRITE" or "LEVEL", which corresponds with the definitions in the file.
I will go ahead and stop here because I can't think of a way to explain this.

If anyone would like the source to what I am blabbering about, merely ask.
[Soon, soon I will release my game engine...].

EDIT: Come to think about, I just remembered that most of my stuff is all global, and maybe I ought to put it in a manager class...
Projects:> Thacmus - CMS (PHP 5, MySQL)Paused:> dgi> MegaMan X Crossfire
With my system, it's critically important to understand that the outside clients who are given handles are never handed a real class instance. A Handle<T> has a very limited set of interfacing options that are available to everybody. It can be compared to other handles for the same resource type, it can report whether it is a valid handle (anybody is allowed to construct an invalid one) and it can be "touched", which updates the timestamp (same idea as the UNIX touch command). The manager allows you to request a new handle, to get the string name associated with a handle, and it can tell you if the related object is currently resident in memory. So the game code does something like:
Entity ent = new Entity();
ent.Model = Renderer.Models.GetModel( "/%current_map/models/chair.spark" );
ent.Transform = Matrix.Identity;
ent.Texture = Renderer.Textures.GetModel( "/%current_map/textures/chair.dds" );
...
//this function does NOT exist, but it gets the point across
Renderer.RenderEntity( ent );
At no point in this code can an actual Model or Texture ever be retrieved. There are simply no functions in the handle or any of the managers that allow you to do it. In the renderer, however...
void Renderer::RenderEntity( const Entity& ent ){    Model* model = m_Models.GetPointer( ent.Model );    if( model == NULL )    {        m_Models.Precache( ent.Model );        return;    }    Texture* tex = m_Textures.GetPointer( ent.Texture );    //continue with actual rendering}
So this GetPointer function is available internally to the renderer which is the one giving out the handles in the first place. The code which gets and holds a handle is not given a pointer. Not ever. There is no need. (Note that this is dual implemented in C# and C++, and C# changes some of the details.)
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
So, to re-word what Promit just said (as I can see you getting confused here!):

The thing that dishes out the handles has a mechanism for retrieving pointers to the data (given a handle), internally.

In his case, it's the Renderer. In yours it'll be the ResourceManager, which I presume will have to be the friend of the Renderer if it's to be of any use. You don't want to expose pointers to resources to the whole program, just allow them to pass around handles without worrying about exactly what they mean. Hooray for encapsulation :)
Here is my resource manager via boost:

#ifndef RESOURCE_MANAGER_H
#define RESOURCE_MANAGER_H
#pragma warning(disable: 4786)
#pragma warning(disable: 64)
#include
#include
#include
#include
#include

template
class Resource_Manager
{

public:

typedef T_ value_type; // std library convention

typedef boost::shared_ptr Resource_Ptr;
typedef boost::weak_ptr Resource_Observer;
typedef std::map Resource_Map;

Resource_Manager() {};
~Resource_Manager() {};

Resource_Observer Request_Resource(const std::string & name)
{
Resource_Map::iterator it = mResources.find(name);

if (it == mResources.end())
{
Resource_Ptr Raw_Resource(new T_);
Raw_Resource->Load(name);
mResources.insert(std::make_pair(name, Raw_Resource));
Resource_Observer Resource(Raw_Resource);
return Resource;
}
else
{
return Resource_Observer(it->second);
}
}

void Request_Resource_Removal(const std::string & name)
{
Resource_Map::iterator it = mResources.find(name);

if (it != mResources.end())
{
mResources.erase(it);
}
}

private:
Resource_Map mResources;
};

#endif

That is probably the most basic of a templated resource manager example I can give, loads objects (resources) that have load functions, returns an observer pointer, pretty simple. Won't repeat resources.

This topic is closed to new replies.

Advertisement