Best design for "dynamic configuration"?

Started by
14 comments, last by coope 8 years, 5 months ago

Hey all,

I'm working on a game engine, and I'm trying to decide the best way to implement configuration/settings/options. Right now, I'm saving these settings to a configuration.ini file. When the engine starts up, I read this information from the file and store it in memory inside a ConfigurationManager class. When the engine shuts down, the settings in memory are written back to the file.

But now comes the part that I'm struggling with. Tons of modules in my engine need access to this information. Input, GUI, camera, etc. all need to know things like window width and height, is it fullscreen, are there shadows, etc. In order to accomplish this, right now I'm simply passing a pointer to the ConfigurationManager around like it's a whore to all the modules that need it, and I'm referencing those configuration settings whenever I need to so that if the settings ever change in real time, the modules update correctly.

As an example, the engine boots up with screen resolution 1280x720, so the GUI creates a orthographic projection with those dimensions. Later we change the settings in real time to 1920x1080. Because the GUI has a pointer to the ConfigurationManager, it can appropriately update this projection with the right width/height.

This may be the best way to approach it, but this seems really sloppy. Are there best practices for approaching this sort of issue?

Advertisement

Why not just make ConfigurationManager functions globally available? Why must it all be referenced from a class?

Simply define your functions like this:


namespace ConfigurationMgr {
 
// returns a 2d vector of width and height of the current window
Vector2 GetWindowDimensions();
 
// returns true if in fullscreen mode 
boolean IsFullScreen();
...

And, in you class where it's needed, just call:


Vector2 WindowDim = ConfigurationMgr::GetWindowDimensions();

That my personal opinion on things that many sub systems need, just keep it simple, and make it a globally available function.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

I would instead create a "PersistenceManager".

If you have says a property called "ScreenResolution", you create a class for that, when requested the class register itself in the Persistence manager (wich is kep alive by keeping a reference somewhere), so that when needed it can save new values or save back. It is up to the persistence manager decide if store all data in same file, in multiple files, if keep an index for fast finding of properties or if keep everything live in RAM and then dump back to disk at certain times only.


class PersistenceManager{
  
public:
    void registerProperty(std::string, Property * );
    //...

    void flush(); // store changes/ load from file
};

class ScreenResolution: public Property{
public:
     ScreenResolution( std::shared_ptr<PersitenceManager> mgr){
         mgr->registerProperty( "ScreenResolution", this);
     }
     //...

     void save() override;
     void load() override;
};

class WindowManager{
public:
    WindowManager( std::shared_ptr<ScreenResolution> res){
        res->load();
        setWindowSize(res->getHeight(), res->getWidth());
    }

    void setSize(){
        res->setSize( height, widht);
        res->save();
    }
    //...
};

To you how to fill in implementation details. You could also automate that with a Dependency Injection framework like Boost.DI or Infectorpp2. It could require a bit more time to setup single class for single properties but on the long run it is much more flexible.

Also you are not forced to create single classes for single properties, you can have also more complex classes each one responsible for serialization and parsing of data if needed.


As an example, the engine boots up with screen resolution 1280x720, so the GUI creates a orthographic projection with those dimensions. Later we change the settings in real time to 1920x1080. Because the GUI has a pointer to the ConfigurationManager, it can appropriately update this projection with the right width/height.

This may be the best way to approach it, but this seems really sloppy. Are there best practices for approaching this sort of issue?

Well, if you're trying to minimize modules being passed information they won't ever care about (like the GUI doesn't need to know if there are shadows), you could pass a reference to the actually required values (instead of the whole ConfigurationManager) to each module.

I wouldn't go the global route, it'll just introduce needless limitations on the re-usability of your modules.


I wouldn't go the global route, it'll just introduce needless limitations on the re-usability of your modules.

Can you elaborate why this is the case with respect to getting configuration settings? All my games use very similar (if not exact) configurations, and they all use the same globally available functions to retrieve them.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)


Tons of modules in my engine need access to this information. Input, GUI, camera, etc. all need to know things like window width and height, is it fullscreen, are there shadows, etc. In order to accomplish this, right now I'm simply passing a pointer to the ConfigurationManager around like it's a whore to all the modules that need it, and I'm referencing those configuration settings whenever I need to so that if the settings ever change in real time, the modules update correctly.

this is what one might call "inherently global data".

seems there are two basic approaches:

1. just make it global - not recommended for the faint of heart - makes the code less idiot proof. other modules are dependent on the globals, but its not apparent unless you inspect the code. but its does mean you don't have to pass globals into every method that uses them. once an accepted practice, this is now considered a poor practice due to the less idiot proof issues. still a viable option if you know what you're doing and the number of coders is small (like 1). really not recommended for dev teams with more than 1 coder or coders who haven't grown up with globals. seems the key to using globals is to declare global, but only access as though through an API. in the long run, using globals seems to save some typing in exchange for less idiot proof code.

2. wrap it in an API of some sort which other modules must depend upon. nothing has changed, its still data that lots of modules depend on. but the API makes it more explicit whats going on. thus more idiot proof code. and greater readability. currently considered the "proper" approach. downside is you have to pass everything into every method.

the typical approach is basically what you're doing now. related data goes in some structure which is passed by reference to all modules dependent on it.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


Can you elaborate why this is the case with respect to getting configuration settings? All my games use very similar (if not exact) configurations, and they all use the same globally available functions to retrieve them.

- What if you want to test your modules independently of whatever configurations happen to be set?

- What if you want to have multiple GUI or rendering modules in the same process using different configurations? (for instance, I've used this to render pre-baked environment maps in my world editor).

You *might* be able to make a stronger argument for config settings to be global (compared to other game modules). But honestly, if you make a decision to use global state, there should be clear benefits to doing so. What are the benefits?

Everyone here is way over-thinking this… Just manage your configuration files at the highest level of your engine, then pass only the needed data to the lower-level modules (don't pass around the configuration manager itself). Those parts of your engine don't need to (and shouldn't) know about the entire configuration from the file, only the data relevant to the module's purpose.

E.g. when the window is first initialized or its size changes, the top-level engine should call graphicsSystem->resize( newSize ) or something similar to update the graphics state. This also avoids the need to constantly poll the settings to see if anything has changed.

Everyone here is way over-thinking this… Just manage your configuration files at the highest level of your engine, then pass only the needed data to the lower-level modules (don't pass around the configuration manager itself). Those parts of your engine don't need to (and shouldn't) know about the entire configuration from the file, only the data relevant to the module's purpose.

E.g. when the window is first initialized or its size changes, the top-level engine should call graphicsSystem->resize( newSize ) or something similar to update the graphics state. This also avoids the need to constantly poll the settings to see if anything has changed.

I think you and Norman Barrows really helped me to see what might be the best solution in this case. I think I'm on the right track, but I definitely see the benefit in splitting up the ConfigurationManager into lower-level modules to be passed around. For example, I might create a ScreenResolution module that holds the width, height, and fullscreen values while a Graphics module might hold information about AO and shadows. This way each module can get the piece of the ConfigurationManager it needs without having access to the whole module itself.

You also mentioned that I shouldn't be polling the settings to see if anything has changed. Having gone back through my code I see that I'm actually doing this about half the time (like in GUI and Input), but other modules are simply being updated when they need to be (like the window). I'll see if I can't change this and have it uniformly update "on demand" throughout my code.

When the engine starts up, I read this information from the file and store it in memory inside a ConfigurationManager class. When the engine shuts down, the settings in memory are written back to the file.

Alot of applications do that. Unfortunately, that means if the game crashes during gameplay for unrelated reasons, all your control settings, display settings, and any other configuration state gets lost and has to be manually re-entered. And usually, if it crashes once, it's going to crash multiple times, so you have to re-configure everything multiple times. It's very annoying. I'd rather they were saved when changed.

On the other hand, if a changed configuration is what led to the crash (though this is less common in my own experience as a player/user), then you don't want to save the settings that caused the crash. But I don't see how that can be avoided, so instead, I'd just keep the "Default settings", "Last known possibly-good settings" (saved at shutdown), and "Most recent settings", and when the game crashes (with an unknown crash) on next startup offer all three options. That might be over-engineering though. =P

This topic is closed to new replies.

Advertisement