Getting the rid of managers

Started by
13 comments, last by way2lazy2care 12 years, 5 months ago
Thank you very much Gamedev for providing me food for thought. This is a follow-up on Questions about managers to avoid hijacking the thread.

It's a real shame, but I do have a few managers. I tried to not call them that way, but I couldn't figure out anything so I just gave them a quick name and carried on. I sometime split them with "moderate" success but they resist in existing. I am really ashamed to say but they are truly ugly! Not only they are "some sort of managers" but they don't even got a nice design.

Pearl number 1: [font="Courier New"]ImageManager[/font]. This resisted because I'm lazy and I don't want to check out the file again. I suppose I might change it tomorrow to [font="Courier New"]ImageLoader[/font]. This is not so bad after all. Only thing it does: reference-count image loads.

Pearl number 2: [font="Courier New"]BasicManager : protected CallMapper, protected base::ImageManager[/font].
[source='cpp']/*! The "basic" manager deals with "basic" resources. ...*/[/source]If I look back, I cannot recall what taken me there... this thing does pretty much nothing but manage "waves"... which are probably what you're thinking about right now. There is some processing involved. There's quite a bit of interaction between this thing and the scripting systems as well as shader management. I therefore suppose [font="Courier New"]WaveSomething [/font]could be a better name (I'd probably also make it a component instead).


Pearl number 3:[font="Courier New"] AdvancedManager : protected BasicManager, protected FlexMaterialManagerInterface[/font]
This was really big some time ago so I split out a [font="Courier New"]GameWorldSystem [/font]class. I'm fairly happy with the GWS class, it seems it has its right to exist. Tomorrow I will rename [font="'Courier New"]FlexMaterialManagerInterface [/font]to [font="'Courier New"]FlexMaterialLoaderInterface[/font].
What does this manager do? It loads "more advanced resources". Very informative.
This has quite a lot of interactions with the scripting subsystems, the management is not always trivial as I'd like to. I expect to need more logic here in the near future (so it will be even less trivial) the requirements of this future expansion are not quite clear yet. So far I've been leaving it as is waiting for more focused semantics to rise from the thing.

Since I'll need to touch this last one "soon" I guess it's a good time to begin thinking at it. Suggestions are welcome.

Previously "Krohm"

Advertisement
It's a real shame, but I do have a few managers. I tried to not call them that way, but I couldn't figure out anything so I just gave them a quick name and carried on. I sometime split them with "moderate" success but they resist in existing. I am really ashamed to say but they are truly ugly! Not only they are "some sort of managers" but they don't even got a nice design.

As in the other thread, a class that manages stuff isn't a bad thing. It can be a good thing. The issue is when you try to turn it into a singleton.

Pearl number 1: [font="Courier New"]ImageManager[/font]. This resisted because I'm lazy and I don't want to check out the file again. I suppose I might change it tomorrow to [font="Courier New"]ImageLoader[/font]. This is not so bad after all. Only thing it does: reference-count image loads.[/quote]

Great, it does one thing, you can create as many as you want even if you only happen to need one.


Pearl number 2: [font="Courier New"]BasicManager : protected CallMapper, protected base::ImageManager[/font]. /*! The "basic" manager deals with "basic" resources. ...*/
If I look back, I cannot recall what taken me there... this thing does pretty much nothing but manage "waves"... which are probably what you're thinking about right now. There is some processing involved. There's quite a bit of interaction between this thing and the scripting systems as well as shader management. I therefore suppose [font="Courier New"]WaveSomething [/font]could be a better name (I'd probably also make it a component instead).
[/quote]
Yes, it does not do one task. It does multiple ambiguous tasks.

Is it a custom container for images? Is it a custom container for wave objects? Does it coordinate the the availability of resources?

Define what the thing actually does (without the word manage) and it should help. If you cannot, it is possible that you have given it too many responsibilities. Break it up into smaller individual tasks.


Pearl number 3:[font="Courier New"] AdvancedManager : protected BasicManager, protected FlexMaterialManagerInterface[/font]
This was really big some time ago so I split out a [font="Courier New"]GameWorldSystem [/font]class. I'm fairly happy with the GWS class, it seems it has its right to exist.
[/quote]
Seems like a renamed version of other stuff.

There is often a simulator class, and it contains instances of lots of pieces that make up the simulator. Sounds like your own re-discovery of this.


Tomorrow I will rename [font="Courier New"]FlexMaterialManagerInterface [/font]to [font="Courier New"]FlexMaterialLoaderInterface[/font].
What does this manager do? It loads "more advanced resources". Very informative.[/quote] Why can't your other resource manager handle this?

The tasks of pre-loading, loading, caching, unloading, and otherwise getting blobs of data into and out of your game is a very common task. It is very frequently implemented as a factory method that works with your own serializers and deserializers.

It does not really matter the type of object if you use a factory. You want to load an animation named foo. You want to load an audio clip named baz. You want to load a model asset named bar. You want to load a UI element, or shader, or whatever else. All of them do exactly the same thing: possibly precache, load, potentially cache or share, and unload.

Any "more advanced resource" is still just a resource, isn't it?


This has quite a lot of interactions with the scripting subsystems, the management is not always trivial as I'd like to. I expect to need more logic here in the near future (so it will be even less trivial) the requirements of this future expansion are not quite clear yet. So far I've been leaving it as is waiting for more focused semantics to rise from the thing.[/quote]

What is a script in your system?

Often a scripting system can be implemented very simply as a state machine.

The script writers get a very basic state machine. There are nodes and transitions. That's it.

Each node can call functions and use data that you expose in your source code or data. It does the thing. Then when whatever condition is specified in the transitions are satisfied, that transition is followed and the machine moves to the next state.

It can be extremely easy to implement, and insanely powerful for script writers who want to invent new ways to break the game.
You go straight to the point here!
As in the other thread, a class that manages stuff isn't a bad thing. It can be a good thing. The issue is when you try to turn it into a singleton.
I think I've got this. Problem is this IS a singleton. I use hashes a lot, the system probably won't run correctly if more than one of those is loaded (it will just load up stuff multiple times).

W1) Is it a custom container for images? W2) Is it a custom container for wave objects? W3) Does it coordinate the the availability of resources?[/quote]W1) No, I used inheritance just because it cut some corners here and there, and I was too lazy to route the calls to a nested object. There indeed was some sense in this choice, ideally those objects would all make up a big "execution environment" providing all the features. Of course that's the same thing by composition... I will look at the code next week and re-factor.
W2) It has some logic involving 'ticking' the waves each time step. Originally, that was not even planned to exist, I was just performing some cleanup and I pulled it out of a bigger class.
W3) No, an external component does.

Although what this does is pretty clear, my main problem with that is finding a name which is short and descriptive. Mainly jargon.
I'm not quite sure I can split it again as it's like 300 LOC.

Seems like a renamed version of other stuff.

There is often a simulator class, and it contains instances of lots of pieces that make up the simulator. Sounds like your own re-discovery of this.[/quote]I don't quite get the point here. Yes, it's just another "piece of something"... so? And what is this "discovery" thing you're referring to?

IM1) Why can't your other resource manager handle this?
IM2 The tasks of pre-loading, loading, caching, unloading, and otherwise getting blobs of data into and out of your game is a very common task. It is very frequently implemented as a factory method that works with your own serializers and deserializers.
IM3) Any "more advanced resource" is still just a resource, isn't it?[/quote]IM1) It was originally part of other managers. It has been pulled out to support some external utilities involving material mangling (mainly the filters).
IM2) Problem was that some resources are terribly complex. I had one whose factory method took about 12 parameters. I initially just grouped those parameters in more logical matters - one of those turned out to be the [font="Courier New"]FlexMaterialLoaderInterface [/font]- I eventually got this down to 4, then figured out that those things could have been useful in a broader topic. They were originally intended to be temporary duct tape but they have been there for a while.
IM3) Yes of course but I don't think I am well aware of what I should do. Perhaps I'm just stuck in the drawn picture. Those objects provide the factory methods. The need to link them together arisen by the interactions of the 'advanced' resources with regards to the basic ones. It's mostly historical and somewhat redundant now that scripting is more pervasive. Yes, I will think at this.

S1) What is a script in your system?
S2) Often a scripting system can be implemented very simply as a state machine.[/quote]There was a state machine based script system in the beginning. There also was another scripting system (I'd likely not elaborate on that, unless it's absolutely necessary). I eventually got mad at the two things and melted them together. Think at a dumb version of UnrealScript. I plan to go back to state-based systems a day but I will likely layer those on top of the current scripting.

Previously "Krohm"

I have a very limited time in which to reply so I can only cover a small portion of your concerns.
[font="arial, verdana, tahoma, sans-serif"]As I have said before, singletons and globals in general create security risks, though I am not speaking only from that standpoint but also from the design standpoint.[/font]
[font="arial, verdana, tahoma, sans-serif"] [/font]
[font="arial, verdana, tahoma, sans-serif"]So let’s assume singletons are not welcome, and you are wondering what you can do to make sure that no more than 1 instance of a class exists.[/font][font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"]Well I designed the in-house engine of a company long ago and I thought that it was common sense not to make 2 CGame instances. A CGame class would create a sound device and some other things that needed to be done only once.[/font][font="arial, verdana, tahoma, sans-serif"]However, one of my coworkers was a programmer, but not a game programmer. For debug issues he was making a second game instance.[/font]
[font="arial, verdana, tahoma, sans-serif"] [/font]
[font="arial, verdana, tahoma, sans-serif"]To my amazement, my sound manager still worked as expected after having been initialized twice, but still this was not a logical course of action.[/font][font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"]So I finally added debug only code that checked to see if a second CGame instance was made. That fixed the problem quite effectively, because most developers work in debug mode until near the end of the project.[/font][font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"]So there is a way to ensure that only one instance of your class is created, even without using a singleton, and without creating global variables that can be exploited on release. These global variable exist only in debug mode, so they act to both protected from incorrect behavior and doing so without creating security leaks in release mode.[/font]
In my design, singletons are not needed for sound managers etc., because the CGame class, which must be created in order to use my engine, creates that pointer itself, and every state of the engine receives a pointer to the game class and can therefore reach the sound manager, or whatever other manager there needs to be.
[font="arial, verdana, tahoma, sans-serif"] [/font]
[font="arial, verdana, tahoma, sans-serif"] [/font]
[font="arial, verdana, tahoma, sans-serif"]L. Spiro[/font]

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid



[font="arial, verdana, tahoma, sans-serif"]So let’s assume singletons are not welcome, and you are wondering what you can do to make sure that no more than 1 instance of a class exists.[/font][font="arial, verdana, tahoma, sans-serif"] [/font]

If you want to make sure that no more than 1 instance of a class exists, why not use a singleton? That's what they're meant for, after all. If singletons are "not welcome", but it's OK having classes that can only function properly when there's max one instance, then that's just hypocrisy.


[quote name='YogurtEmperor' timestamp='1319277790' post='4875301']
[font="arial, verdana, tahoma, sans-serif"]So let’s assume singletons are not welcome, and you are wondering what you can do to make sure that no more than 1 instance of a class exists.[/font][font="arial, verdana, tahoma, sans-serif"] [/font]

If you want to make sure that no more than 1 instance of a class exists, why not use a singleton? That's what they're meant for, after all. If singletons are "not welcome", but it's OK having classes that can only function properly when there's max one instance, then that's just hypocrisy.

[/quote]

Singletons are to be used for a case where there can only be one instance of the class without causing erroneous or incorrect behaviour. It's not a catch-em-all insurance policy against having > 1 instances. Sure, it can be used that way, if you really want -- just like globals can be used for things that aren't truly needed globally.

It's just not a terrific idea to do so. Especially when it comes down to: "I think there should never be need for two instances of this."
"I will personally burn everything I've made to the fucking ground if I think I can catch them in the flames."
~ Gabe
"I don't mean to rush you but you are keeping two civilizations waiting!"
~ Cavil, BSG.
"If it's really important to you that other people follow your True Brace Style, it just indicates you're inexperienced. Go find something productive to do."
[size=2]~ Bregma

"Well, you're not alone.


There's a club for people like that. It's called Everybody and we meet at the bar[size=2].

"

[size=2]~

[size=1]Antheus
Getting back to the GWS object, that is just fine. Many games will have a main simulator object and will work just fine with it. Your re-discovery is not an issue.


As for the singletons, the business world and TDD advocates naturally solve it by their basic philosophies. Many game developers get stuck in the emotional rut that there is only one game window shown on the screen at a time, therefore singleton.


Most well-designed games I've worked on don't have this issue.

Some experiments to help you:
One great trick to split screen and certain graphical effects is to run two different view windows at once. Can you run two windows? If not, why not? There is no physical reason why you cannot run multiple windows on the screen; that is exactly what it is made for. What about split screen within a single window? If not, why not? Your display area is normalized to a rectangle, why should it matter if the rectangle is only a portion of the physical screen? Why should you be restricted to a single rectangle instead of two or three or nine? The restriction is artificial and probably unnecessary.

One of the best in-house engines I worked with had two views on screen; one showed the regular game, the other was exactly the same view with alternate visualization: overdraw, physics objects, physics motion displays, and x-ray vision were the most useful to me. Any fundamatal reasons your game engine cannot do that? Why not?

Can you have one game instance run multiple simulations internally? If not, why not? It can make network play easier to debug since you can eliminate the difficulties of networking and simply hae direct IPC between the systems. You should be able to have a collection of game simulations all running currently (even if it is slow). Automted testing (either test scripts or stress tests) could start 30+ instances of the simulator and run them all at once.

Can you have multiple instances of your resource manager? If not, why not? When debugging and stress testing it is sometimes useful to have multiple instances. Sometimes it is useful to have one instance pulling from the main resource location and a second instance pulling from a debug resource location.


At home I use TDD so this isn't an issue because everything must be instanced rather than globals or singletons. One way to do this is have everything come from the game simulator. The simulator has clocks. The simulator has resource loaders. The simulator has everything neccesary to run the game. The simulator has an interface for display, but does not have a view (that is secondary to running the game). Now create and run two or more independant simulators.
Well since we are discussing uses for managers as in when they are good. How about a manager that loads, starts, shuts down, and unloads dynamic libraries containing separate systems (graphics, network, etc)?

My code (ignore the name as core does have meaning inside the design for the whole program. I have removed includes as they are irrelevant to the discussion of managers.

// Preprocessor selection based on OS
#ifdef _WIN32
#include <Windows.h>
typedef HMODULE DLLHANDLE;
#else
#include <dlfcn.h>
typedef void* DLLHANDLE;
#endif

class CoreManager {
public:
CoreManager(GlobalProperties* gprops, MessageDispatcher* msgdisp);


void Startup(std::string name = "");
void Shutdown(std::string name = "");
void Load(std::string name);
void Unload(std::string name = "");

void Update(double dt = 0.0f);
private:
GlobalProperties* gprops;
MessageDispatcher* msgdisp;

std::map<std::string, CoreInterface*> coremap; // A mapping of each core to its given name
std::map<std::string, DLLHANDLE> libraries; // A mapping of each loaded library to a given filename
};



CoreManager::CoreManager( GlobalProperties* gprops, MessageDispatcher* msgdisp ) : gprops(gprops), msgdisp(msgdisp)
{

}

void CoreManager::Startup(std::string name /*= ""*/ )
{
if (name == "") {
// We need to use the regular for loop as we may modify the iterators if a core fails to start
for (auto it = this->coremap.begin(); it != this->coremap.end(); ++it) {
if (!(*it).second->Startup()) {
//EventLogger::printToFile( 5,"Core failed to start removing.", 'C'); // TODO: Fix eventlogger
(*it).second->Shutdown();
delete (*it).second;
this->coremap.erase(it++);
if (it == this->coremap.end()) {
break;
}
}
}
this->msgdisp->SetCores(&this->coremap);
} else {
if (this->coremap.find(name) != this->coremap.end()) {
this->coremap[name]->Startup();
}
}
}

void CoreManager::Shutdown( std::string name /*= ""*/ )
{
if (name == "") {
// We need to use the regular for loop as we may modify the iterators if a core fails to start
for (auto it = this->coremap.begin(); it != this->coremap.end(); ++it) {
(*it).second->Shutdown();
delete (*it).second;
this->coremap.erase(it++);
}
} else {
if (this->coremap.find(name) != this->coremap.end()) {
this->coremap[name]->Shutdown();
delete this->coremap[name];
this->coremap.erase(name);
}
}
}

void CoreManager::Load( std::string name )
{
if (this->libraries.find(name) == this->libraries.end())
{
#ifdef _WIN32
wchar_t buf[256];
mbstowcs(buf, name.c_str(), name.length());
HMODULE libdll = LoadLibrary(buf);

this->libraries[name] = libdll;
#else
void * libdll = dlopen(fname.c_str(), RTLD_LAZY);
this->libraries[name] = libdll;
#endif
}

CoreFactory fact;
#ifdef _WIN32
fact = (CoreFactory)GetProcAddress(this->libraries[name], "CoreFactory");
#else
fact = (CoreFactory)dlsym(this->libraries[name], "CoreFactory");
#endif

CoreInterface* core = fact(this->gprops, this->msgdisp->GetCallback());
this->coremap[name] = core;
}

void CoreManager::Unload( std::string name /*= ""*/ )
{

bool ret = false;

if (this->libraries.find(name) != this->libraries.end())
{
Shutdown(name);
#ifdef _WIN32
if (FreeLibrary(this->libraries[name]) != 0)
{
ret = true;
}
#else
ret = dlclose(this->libraries[name]);
#endif
if (ret)
{
this->libraries.erase(this->libraries.find(name));
}
}
}

void CoreManager::Update( double dt /*= 0.0f*/ )
{
for (auto it = this->coremap.begin(); it != this->coremap.end(); ++it) {
(*it).second->Update(dt);
}
}
In my design, singletons are not needed for sound managers etc., because the CGame class, which must be created in order to use my engine, creates that pointer itself, and every state of the engine receives a pointer to the game class and can therefore reach the sound manager, or whatever other manager there needs to be.[/quote]Thank you very much! It's exactly the same rationale I used to design mine! Everything is a part of the bigger core object. The object is instantiated using a global systemwide lock so no chance of double instantiation can happen. It's always good to know I'm kinda on a good path.

I begin being positive perhaps there's no real problem after all. Besides naming. I'd still want some suggestions for the WaveSomething... perhaps WavePooler would do ?_?

WM1) Can you run two windows? WM1A) If not, why not? There is no physical reason why you cannot run multiple windows on the screen; that is exactly what it is made for. WM2) What about split screen within a single window? WM2A) If not, why not? WM2B) Your display area is normalized to a rectangle, why should it matter if the rectangle is only a portion of the physical screen? WM2C) Why should you be restricted to a single rectangle instead of two or three or nine? The restriction is artificial and probably unnecessary.
...
WM3) One of the best in-house engines I worked with had two views on screen... Any fundamatal reasons your game engine cannot do that? Why not?


[/quote]WM1) No. The system was able to do that in very old versions.
WM1A) It just collected dust with no apparent benefit. It was originally implemented as it was deemed useful WRT previous experience - it was a different field. As a side question - how's going with sharing renderers across multiple windows? As far as I recall direct3D9 is a bit quirky on that, for GL I am not sure but I think not (it's bound to the Device Context which is a per-window property). If this involves creating multiple renderers... that's going to be a big no. I think your statement is a generalization a bit too broad. I don't recall a single PS3 game I've played showing multiple windows per screen. I have also seen some games not only assuming a single window but also allowing a single resolution - I don't see this being a problem for me in the real world for the next years.
WM2) Not currently supported. Plan is to just expose an additional renderer control (it will likely be script-driven).
WM2A) Not necessary now.
WM2B) I disagree with this. What is this normalized rectangle? How do you deal with it on 4:3? What on 16:10? What about stretching. No way. I work in physical inches, or perhaps pixels. If the user (script) wants to cut it in ratios, that's his/her choice. Design units are in pixels at 96dpi. The alternative, offset-scale normalized coords seems unnecessarily convoluted. Perhaps it's just me, but I didn't like the normalized coord approach at all in the past. I couldn't stand the stretching.
WM2C)Of course when it is scripted there will be N (I don't care). This day is not now. I disagree with your statement; I don't quite see the connection with the original problem.
WM3) Yes, each day got only 24 hours, priorities and trade-offs will have to be made; I think that is a major fundamental reason.

Can you have one game instance run multiple simulations internally...The simulator has clocks. The simulator has resource loaders. The simulator has everything neccesary to run the game. The simulator has an interface for display, but does not have a view (that is secondary to running the game).[/quote]Yes, totally awesome and... what's the difference between my system and yours? And what's the deal of running it multiple times in the same process? Why not just ran them sequentially... in parallel chunks? It sounds like write about "accuracy" as a mathematician might write about: I don't see the point of having more than adeguate accuracy. I understand nothing of what you're writing about. Please try to be more concise and propose real world examples (excuse me, but I am not well aware of what you do).
I don't want to write an "engine"... I want to get the job done. I will implement "nicely" whatever it takes to get the job done. Everything else will wait. Long term investments will be considered... in a long term perspective. OP is NOT about a long term perspective it is about a simple solution to a problem I have. Now.

Previously "Krohm"


Pearl number 1: [font="Courier New"]ImageManager[/font]. This resisted because I'm lazy and I don't want to check out the file again. I suppose I might change it tomorrow to [font="Courier New"]ImageLoader[/font]. This is not so bad after all. Only thing it does: reference-count image loads.

Sounds like an ImageCache.

I'll suggest to extract the construction of resources away from these classes. Then you have ImageCollection / ImageCache, BasicImageLoader and AdvancedImageLoader. Or maybe just free functions for construction of resources. Or, If the construction is complex process, you could try builder pattern where you can pass short lived builder objects around that can retain the state until the result is finished. These builders can even be polymorphic.

A lot of times people subclass just to be able to add different constructor, while the public interface of the end result remains the same.

This topic is closed to new replies.

Advertisement