7
\$\begingroup\$

I currently have a ResourceManager class which is responsible for searching for resources with a given identifier, returning a reference counted pointer to the resource if a valid resource is found, and instantiating a new object if none has been found. Often a resource itself depends on other resources, which are requested from the resource manager. For example, Window may depend on SdlMain (which initializes and shuts down SDL), and thus the constructor of Window would ask ResourceManager for the SdlMain object.

Example:

ResourceManager resources;
//the return type is std::shared_ptr<Window>, the first argument "MainWindow" is the 
//..ID to search for, the rest of the arguments are simply forwarded to the constructor 
//..of class Window if needed.
auto mainWindow = resources.make<Window>("MainWindow", "Hello World",
                    512, 512, SDL_WINDOW_SHOWN, SDL_RENDERER_ACCELERATED |
                    SDL_RENDERER_PRESENTVSYNC);
//The Window object contains the renderer, too, so the ID of the current window 
//"MainWindow" must be passed to the texture's constructor
auto tex1 = resources.make<Texture>("tex/tex1.png", "tex/tex1.png", "MainWindow");
//This renders the texture to the screen.
tex1->renderToBuffer(0,0);
mainWindow->bufferToDisplay();

However, the requirement of knowing the actual ID of the Window when creating a texture becomes a problem when writing a sprite class, if multiple window objects are to be supported:

//Unlike Texture, a Sprite is not a resource but it's lifetime is bound to the  object
//..owning it (and there may be many sprites using the same Texture). Thanks to 
//..how Texture is implemented, now everything that wishes to use a sprite must be 
//..aware of the ID of the window "MainWindow" in order to initialize a Sprite.
Sprite sprite(resources, "tex/tex1.png", "MainWindow"); 

I recently came up a solution for this. I would give the identifiers a context, similar to the "scope" of programming languages. SdlMain would be found in global context, with it's ID "SdlMain" available and meaningful anywhere, while one window may reside in context Game, and a second window may reside in the context Editor. Both Window instances would have the name "Window", but there would be no conflict since they reside in a different contexts. The resulting system might look somewhat like this:

ResourceManager resources;
//Set the scope to "Game"
resources.setContext("Game");
auto mainWindow = resources.make<Window>("Window", "Hello World",
                    512, 512, SDL_WINDOW_SHOWN, SDL_RENDERER_ACCELERATED |
                    SDL_RENDERER_PRESENTVSYNC);
//Texture asks ResourceManager simply for a "Window", with no idea about the current
//context.
Sprite sprite(resources, "tex/tex1.png"); 
sprite.draw();
mainWindow->bufferToDisplay();
//Set the context back to global 
resources.setContext();

The actual ID search would function as follows: First the ID is searched for in the current context, if there are no matches the preceding context is evaluated too, until the search arrives at the global context.

Note that the final version would use integer ID:s instead of strings. I do realize that this might be the best example of overengineering ever, but I'm mostly doing this for its learning value.

Before fully committing to such a system, I would like to know some opinions on the matter from more experienced programmers:

Is this a practicable solution? Are there any blatant flaws in this concept? Any suggestions?

\$\endgroup\$
  • \$\begingroup\$ I'm not certain if this question should be on Programmers StackOverflow. I only mention that since you might get more answers there. I voted you up since it was a good question and well thought-out. \$\endgroup\$ – Daniel Hollinrake Feb 18 '14 at 18:19
  • \$\begingroup\$ Originally this was posted on GameDev/StackExchange, but it was soon flagged (admittedly correctly) as too broad, so I deleted the original one. Thanks for the suggestion. I guess cross posting on programmers is not a good idea at this point. \$\endgroup\$ – jms Feb 18 '14 at 18:23
  • \$\begingroup\$ I like the idea of "scoping", because it allows you to create multiple identical object hierarchies which are separated from each other. Each scope could have resources and sub-scopes (which in turn are a scope). I thought about suggesting something like boost.property_tree for this, but it doesn't provide a parent() method which you would need for lookup in the next more general scope. \$\endgroup\$ – Christoph Feb 18 '14 at 20:06
5
\$\begingroup\$

Preface: these are just my personal opinions based on experience.

Don't force the user of the class to remember to do stuff

The setContext(const char*) method is a method which the user of the class is required to call to have proper operation. As such the user is at risk of forgetting to call this method and thus introduce a bug in the software. Further more the user must remember to be consistent in spelling of the context name or face the risk of introducing subtle bugs where different contexts are used by mistake.

If you wish to go with the concept of contexts (say that 10 time quickly), I would make a "context" a first class object which is required for creating instances of resources. Like this:

class ResourceContext{
public:
    template<typename... Arg>
    std::shared_ptr<Resource> make(Arg... args);
};

class ResourceManager{
public:
    std::shared_ptr<ResourceContext> createContext(const char*);
    std::shared_ptr<ResourceContext> findContext(const char*);
private:
    friend class ResourceContext;
};


ResourceManager mgr;
auto gameContext = mgr.createContext("Game");
Sprite sprite(gameContext, "tex/tex1.png");

This approach solves my two worries

  1. You can not forget to change context. You are forced to find the correct context.

  2. You can not silently introduce a typo, either you explicitly create a new context or you look up an existing one (with an error if it doesn't exist).

Lookup by reference when possible

If your texture/sprite class is dependent on the window they are displayed in (as opposed to the SDL context) then it is quite reasonable to expect them to be created from a context where the window is available. Why not simply pass an instance of the resource<Window> to the constructor of the sprite/texture instead of doing a lookup by string ID?

Your current approach implicitly reserves "magic" names for specific instances of specific classes the risk here is that an unknowing user may create some kind of conflict because they didn't know of all the reserved names in your system.

One size doesn't fit all (Stop sugar coating my globals)

In general I believe that this kind of resource manager should only be used for obtaining external resources such as textures, sounds etc. To me it looks like you're trying to sugar coat having global variables, because that's essentially what this is being used like in your example. Using it for SDL context is overkill, why would you want to shutdown SDL during any point of your program's lifetime? What is the point of having it managed as a resource over just a simple global?

In all honesty, I think you are over-engineering this.

\$\endgroup\$
  • \$\begingroup\$ The resource manager itself is necessary since the list of textures, sounds etc. is loaded at startup, and the managed resources are only loaded when needed. Then I thought that I could extend it to external API concepts too, like SDL initialization and window management, into a single uniform system. This is not going to be used for managing anything internal to the program. I do realize that this is basically a second, runtime type system (without type safety) that has the equivalent of globals, and I will end up in a header full of ID constants that is included everywhere. \$\endgroup\$ – jms Feb 18 '14 at 21:16
  • \$\begingroup\$ Yes, that's the typical use of this kind of resource manager (as well as keeping recently freed objects in memory for a while just in case they are requested again soon). Yes, so why do you want this system over a global for those external APIs? What would you gain other than engineering satisfaction? :) \$\endgroup\$ – Emily L. Feb 18 '14 at 21:22
  • \$\begingroup\$ I found that the general idea as used in your proof-of-concept solution addresses the problem very nicely, packaging the whole context system behind a rather simple front-end with very little overhead. Would "scope", "namespace" or "domain" be a more suitable name than "context"? At this stage the only thing that I'm really striving for is just that, engineering satisfaction :D \$\endgroup\$ – jms Feb 18 '14 at 21:40
  • \$\begingroup\$ Uhm, "scope" has more of a temporary sense as in scope of variables on the stack. And "domain" isn't quite it either. I think "context" is good, or possibly "namespace"... actually I think I prefer "context" :) \$\endgroup\$ – Emily L. Feb 18 '14 at 22:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.