Assets: When to Actually Load?

Started by
12 comments, last by kloffy 13 years, 5 months ago
I have an "Asset Manager" class which maintains GUIDs and supplies resource data to the API-specific rendering interface's container classes.

Where is the best place to actually USE this though? I really don't want to be passing an instance of the asset manager around excessively, and making it singleton seems like it would encourage calls from "bad states".

For instance:
calling Entity.SetModel("path/to/model.mdl")

how would the Entity class know anything about the specific instance of Asset Manager?

or just:
setting Entity.Model = "path/to/model.mdl"

and letting the rendering interface notice it wasn't loaded, thus making the call to asset manager?

[Edited by - Shanjaq on October 16, 2010 8:30:38 AM]
Advertisement
Great question! I've been trying to get my head around this for a good week and I have yet to find a nice solution. Good to hear that I am not the only one having this problem.

My setup looks something like this: I have an Application class which creates the window and the graphics context (in my case OpenGL). This Application class manages a stack of Screens, which represent the high-level states in my game (splash, main menu, in game...). I figured that a nice place to put such an "Asset Manager" would be this Screen class, every Screen would manage its own assets. There could be some duplication between Screens, but let's assume this is not a problem for now. Ok, so in order to render something, I have a rudimentary scene graph consisting out of GroupNode (extends Node) and ObjectNode (extends Node) objects. The ObjectNodes have actual Assets (Mesh, Texture...) associated with them. And this is where I've been going back and forth for a while now: Should the ObjectNodes have a reference to the "Asset Manager"? If so, they would somehow need to be associated with the Screen at some point. However, what about ObjectNodes that haven't been associated with a Screen yet? They wouldn't have reference to a "Asset Manager" and couldn't load assets. Passing the "Asset Manager" around doesn't seem like such a great option either. Perhaps the ObjectNodes shouldn't have a reference to the "Asset Manager" at all?

Anyways, I'm sorry that I couldn't offer any answers. I posted in hopes of making the discussion more concrete and hearing if people have different approaches. I've only mentioned the most basic things and kept them generic, so I expect most of you have such elements in your programs in some way, shape or form. I hope it doesn't derail the thread.
No derailing detected. I have a huge tome on "Game Engine Architecture" and it describes all of the functions of a run-time asset manager, but fails to provide any context as to where it could be used. A Sequence Diagram would be most helpful.
How about, rather than set the path to model, give the model from the outside?

as in:

Model mdl = assetManager.getModel("path/to/model.mdl");Entity.setModel(mdl);


this way, Entity class doesn't care about asset manager at all, it just wants Model instance, no matter where it comes from
OpenGL fanboy.
Quote:Original post by i_luv_cplusplus
How about, rather than set the path to model, give the model from the outside?
That is one of the approaches I have considered. I kind of like it. However, it means that it isn't possible to code like this (using the setup I described earlier):

public class EnemyNode : ObjectNode{    public EnemyNode()    {        Mesh = AssetManager.Load<Mesh>("enemy.obj");        Texture = AssetManager.Load<Texture>("enemy.png");    }}public class GameScreen : Screen{    private long lastSpawnTick;    public override void Update(long Tick)    {        if (Tick - lastSpawnTick > 5000)        {            Root.Add(new EnemyNode());            lastSpawnTick = Tick;        }    }}

But it would look something like this:

public class EnemyNode : ObjectNode{}public class GameScreen : Screen{    private long lastSpawnTick;    public EnemyNode CreateEnemy()    {        var enemy = new EnemyNode();        enemy.Mesh = AssetManager.Load<Mesh>("enemy.obj");        enemy.Texture = AssetManager.Load<Texture>("enemy.png");        return enemy;    }    public override void Update(long Tick)    {        if (Tick - lastSpawnTick > 5000)        {            Root.Add(CreateEnemy());            lastSpawnTick = Tick;        }    }}

Now, the big question is, what are the advantages/disadvantages of either method. Obviously, this is a very simplified example which certainly does not capture all the intricacies of the subject matter...
OOP (object Oriented Programming) is all about organization. For example, should an EnemyNode like you described above know about the Assetmanager? Well, there is a hierarchy of class-to-class knowledge that must be maintained, and some classes should not know about other classes (this is obvious, isn't it?). To use a crazy example, a std::vector<T> should not have knowledge of the class EnemyNode because the vectors JOB is to just hold "things" regardless of their type --thank you for templates!

So, your EnemyNode SHOULD have knowledge of how to load itself and SHOULD have knowledge of how to do that. So, it SHOULD have knowledge of the AssetManager, otherwise it cannot load itself.

Now, there are instances where this cannot be, it depends on what you are doing. If you are writing a video game for example, then you are more flexible with class-to-class knowledge because the program is designed to run together as a whole, so it wouldn't make sense to have hard separations between some classes. I will get some opposition on this topic because it is all about personal choice.

Writing good code is about a balance: just make sure you do not get out of hand and let all classes know about all other classes, that is a horrendous mistake. The whole point behind writing a class is that it has a JOB TO DO, and you have to give it the tools, and knowledge it needs to complete that JOB. If you want to pass that information into the class through a function call from an outside class, that is fine. If you want to give the class the knowledge to go straight to the AssetManager, that is fine to.

Just do not go crazy like I did in this response :P

This piece of code is wrong
public class EnemyNode : ObjectNode
{
public EnemyNode()
{
Mesh = AssetManager.Load<Mesh>("enemy.obj");
Texture = AssetManager.Load<Texture>("enemy.png");
}
}

Unless you want to have one enemy, this wont work. You should have a manager class for Mesh, Sound, Textures, Physics, etc. Then these Manager classes should take care of the loading, and unloading of their children, playing or setting up of rendering. It should be like a tree. At the top is the Parent, who tells each of the children --MeshManager, SoundManager, PhysicsManager-- to load themselfs, then each loop should be an update called on each of the managers followed by a Run, or you can combine it into a Run call. Each of the managers will then call on their children and update and do their work . . etc

Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
Quote:Original post by smasherprog
OOP (object Oriented Programming) is all about organization. For example, should an EnemyNode like you described above know about the Assetmanager? Well, there is a hierarchy of class-to-class knowledge that must be maintained, and some classes should not know about other classes (this is obvious, isn't it?). To use a crazy example, a std::vector<T> should not have knowledge of the class EnemyNode because the vectors JOB is to just hold "things" regardless of their type --thank you for templates!

So, your EnemyNode SHOULD have knowledge of how to load itself and SHOULD have knowledge of how to do that. So, it SHOULD have knowledge of the AssetManager, otherwise it cannot load itself.


That's a not-entirely-unreasonable approach, but it's not the only one, the "most OOP" one, or probably the best one, either; particularly if you are concerned with having the extensibility to support many model formats.

When we talk about "organization" in an OOP sense we're not talking about putting all the stuff that "feels" like it should go together into one class, we're talking about cohesion and decoupling. These two concepts are basically the crux of the "One class, one responsibility" mantra in object-oriented programming.

The responsibility of an EntityNode (assuming it to be a node in a scene graph) is simply to represent the visual aspect of the entity within the scene. It probably doesn't even hold the actual model data (after all, you don't want to duplicate model data for each instance of an enemy or something) so, why then, would it make sense for it to know how to load something which it does not own?

Then we're left with the model class, whatever that may be. The responsibility of the model class is to represent model data to the underlying graphics API (whether that be OpenGL, Direct3D, or something higher-level, like a scene graph or API abstraction layer.) Even that does not require that the model class know how to load itself, only that it has been loaded at some point.

Loading the model is yet another separate responsibility -- just think about wanting to support another model format, or a new version of an existing format. Why should this require changes to the model class, whose responsibility it is to represent the loaded data to the graphics API? Depending on how well your classes are encapsulated, such changes might cause a massive amount of recompilation to occur, which is something to be avoided.

As for how to structure it and when to use it, I, myself, have either a single, global asset manager (per resource type) whose reference is passed down to each gamestate or I have multiple resource managers (per resource type) owned by the gamestate. In either case resources are loaded at the beginning of each gamestate and none of the resources know anything about the asset manager itself. Loading is accomplished by separate loader classes (function objects) or functions, and specified through a function argument template. That can be cleaned up a bit with the factory pattern if it gets too ugly.

[Edited by - Ravyne on October 16, 2010 11:19:08 PM]

throw table_exception("(? ???)? ? ???");

Just to add to the above, if you are using something like C++ don't feel you need to put EVERYTHING in a class.

Somethings naturally fit better as free functions in namespaces; something the C++ Standard Library shows.

For example, while you might have a 'resouce cache' which knows how to hold onto resources the loading of a resource might well be nothing more than a function in a namespace.

For example;
TexturePtr SomeClass::LoadTexture(const std::string& filename){    TexturePtr texture;    if(!cache.hasEntry(filename, texture))    {        texture = TextureLoader::Load(filename);    }    return texture;}


(Note; the above code should not be considered 'best practise' its just an example)
And to add back again to Phantom's input, that is precisely why the loader in my own code (as mentioned) is passed as a template function argument -- this gives compile-time polymorphism allowing the loader to be a function object (a class which overloads 'operator ()' taking some defined parameters to act like a function) or an honest-to-god, free-standing function. Most of the loaders I implement are indeed single functions (for example, .txt, .bmp, .pcx, etc), but its nice to organize more complicated formats that might benefit from helper functions as a class (when data sharing is prominent, otherwise simple, free-standing helper functions can work too) or to pass additional paramters through the class constructor which are not accounted for in the load function's interface.

throw table_exception("(? ???)? ? ???");

Just wanted to say that I appreciate the input and I agree with much of what has been said. However, I'm having difficulties seeing how some of those abstract suggestions would translate into practice. Encapsulation, decoupling, having clear responsibilities assigned to classes etc. are definitely things to strive for. What would be of even greater value is an architecture diagram or a sequence diagram (as shanjaq suggested) in order to get a clearer picture: what classes hold references to the asset manager, when are those references established, at what point are assets loaded/released, how are hierarchical assets dealt with and so on. (Also, the design of the individual loaders seems fairly clear to me, so that should not be the issue here.)

This topic is closed to new replies.

Advertisement