Skip to main content
GameDev.net gamedev.net
🔒 Locked

Best design for "dynamic configuration"?

Started by trentfaris242 Nov 3, 2015 at 4:15 PM 14 replies 4.8k views
Original Post
trentfaris242
trentfaris242

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?

BeerNutts
BeerNutts

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)
DemonDar
DemonDar

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.

phil_t
phil_t




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.

BeerNutts
BeerNutts




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)
Norman Barrows
Norman Barrows

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"</
phil_t
phil_t




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?

Aressera
Aressera

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.

trentfaris242
trentfaris242

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.

Servant of the Lord
Servant of the Lord

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

Norman Barrows
Norman Barrows

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.

the thing to do is to FIRST define the APIs that will need the data such as width,height. this will then tell you exactly what kinds of chunks of data you need to pass around.

"this method needs w,h. so do a lot of other ones. but some need w,h plus another variable, and some need entirely different stuff...."

look for repeating patterns of different methods using the same set of "global" variables. this will tell you which variables you want in the structs you pass to the methods. with luck you can get by with perhaps just two or three struct types for passing parameters.

my in-house game library uses a drawinfo struct for drawing calls, and Caveman uses a location struct for setting (indoor,outdoor,etc) + world coordinates + rotation.

note that structs just for passing parameters is how a lot of directx code works.

Norm Barrows Rockland Software Productions "Building PC games since 1989"</
Norman Barrows
Norman Barrows




You also mentioned that I shouldn't be polling the settings to see if anything has changed.

ideally, call hierarchy and parameter passing will guarantee that whatever value is passed in is current and valid.

Norm Barrows Rockland Software Productions "Building PC games since 1989"</
DemonDar
DemonDar

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.

Exactly what I sad :) thanks. I just showed the correct way to inject a constructor dependency on that. Just repeating because seems I posted a too long snippet and people skipped over that.

trentfaris242
trentfaris242

This has been a really great thread. Thanks so much for the input, everyone!

Zipster
Zipster

I would just use JSON for all this, giving each system that needs to save configuration settings ownership of a JSON object and allowing it to structure its configuration data as it sees fit. Whenever the object changes, you flag the settings as dirty via a mechanism of your choosing and write them out at some point later. Example:


class Graphics
{
public:
   Graphics(JSONObject& settings) : _settings(settings)
   {
      setSize(_settings["width"].getInt(), _settings["height"].getInt());
   }
   
   void setSize(int width, int height) { ... }
   
private:
   JSONObject& _settings;
};


class Input
{
public:
   Input(JSONValue& settings)  : _settings(settings)
   {
      setKeyboardLayout(_settings["keyboardLayout"].getString());
   }
   
   void setKeyboardLayout(const std::string& layout) { ... }
   
private:
   JSONObject& _settings;
};


int main()
{
   JSONValue jsonSettings = JSONValue::parse(loadFile("config.json"));
   Graphics graphics(jsonSettings["graphics"]);
   Input input(jsonSettings["input"]);


   while (true)
   {
     // ...
  
     if (jsonSettings.isDirty())
     {
        saveFile("config.json", jsonSettings.toString());
        jsonSettings.clearDirty();
     }
  
     // ...
   }

   return 0;
}
coope
coope

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

I agree settings should be saved when they are changed as that is annoying, and it does sometimes lead to those problems. Usually I end up fixing my own play experiences where a setting causes crashes by editing the config file manually. I can easily see a way to avoid that however, which would be a lot easier if there are more legitimate methods for detecting crashes.

You could keep a flag at the start of your config file which specifies if the file was saved to when the game closed. You set the flag to false after the game finishes loading. Thus a false flag value would be interpreted as the game having crashed.

In case you find it is set to false upon loading you can either load the default config file or you can keep two files: backup.ini and config.ini. Write backup.ini when you load config.ini successfully, and read it when you don't. This way you can save directly to config.ini when changes occur without worrying about it, because the shutdown procedure will validate the file for you.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.