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

Extensible settings system

Started by lexs Jan 26, 2008 at 5:26 PM 16 replies 1.6k views
Original Post
lexs
lexs
At the moment i have a Console object which can parse commands etc, to complete it i have a Settings object which allows for easy extraction of variables, like Screen_Width etc. So now i'm thinking about just passing that Settings object to every object that needs it and let them fetch their settings in the constructor. But Source and another engines allow for on the flow changes to values, how do they do this? Do they attach some sort of "updated()" function to every value? How would you implement something like that? [Edited by - lexs on January 26, 2008 5:44:38 PM]
Zakwayda
Zakwayda
Quote:
Original post by lexs
At the moment i have a Console object which can parse commands etc, to complete it i have a Settings object which allows for easy extraction of variables, like Screen_Width etc. So now i'm thinking about just passing that Settings object to every object that needs it and let them fetch their settings in the constructor.

But Source and another engines allow for on the flow changes to values, how do they do this? Do they attach some sort of "updated()" function to every value? How would you implement something like that?
In my game framework, settings are stored and passed around via reference-counted smart pointer. That way, clients always have access to the current value (which, as in your system, can be modified via the in-game console).
Telastyn
Telastyn
Quote:
Original post by lexs
But Source and another engines allow for on the flow changes to values, how do they do this? Do they attach some sort of "updated()" function to every value? How would you implement something like that?


Most of the configurable values are just externally visible properties of a class. The config file is read in and the properties are set by whatever creates the class in question. Runtime changes go straight to the property. The two are fairly independent.
nife87
nife87
Quote:
Original post by lexs
But Source and another engines allow for on the flow changes to values, how do they do this? Do they attach some sort of "updated()" function to every value? How would you implement something like that?


An answer to this might be the observer pattern.
The subsystems, who need to act upon change of the variable, could also listen for variable changes, or they could be become aware of it and then act upon it somehow by using helper listeners - therefore adding of new variables will not require changes to the old subsystems, but only adding/changing new listeners.
lexs
lexs
I came up with this:

class Settings{public:	template <typename T>	void declare( const std::string & name, int def_val, T * obj, void (T::*setter) (int), int (T::*getter) () );};#define DECLARE_SETTING_INT(name, default_value) settings.declare(#name, default_value, this, this->set ## name , this->get ## name );


So in your init/constructor function should look like this:

void Class::init(const Settings & settings){	DECLARE_SETTING_INT(ScreenWidth, 1024)	DECLARE_SETTING_INT(ScreenHeight, 1024)}


You will be required to have a int getScreenWidth and a void setScreenWidth(int) though.
This would work pretty good because in your set function you can reload if needed, and it should be pretty fast as the overhead is almost nil.
Telastyn
Telastyn
Umm... wow. That violates like 4 of the cardinal no-no's in modern C++ in 3 lines of code (don't use macros, don't use shallow get/setters, don't use bald pointers, don't use class function pointers).

If you're going to go that route then templatizing the param type (the int) would make things easier.
niteice
niteice
The code needing that variable, most likely, simply checks its value every time it is run, probably every frame. If the code may not be run every frame, Source allows one to attach a callback function to be executed whenever the value changes.
lexs
lexs
Quote:
Original post by Telastyn
Umm... wow. That violates like 4 of the cardinal no-no's in modern C++ in 3 lines of code (don't use macros, don't use shallow get/setters, don't use bald pointers, don't use class function pointers).

If you're going to go that route then templatizing the param type (the int) would make things easier.


Yeah, i thought i may be some macro abuse, but those aren't shallow get/setters right? Shallow is when they only set the value and do no validation? And my code allows for your own implementation of those get/setters. And about bald pointers, this is only some code i wrote up quickly (there is no implementation yet etc).
Oh, and what's wrong with class function pointers, ie delegates?

I guess you could remove that macro and let the user just pass their own set/get function instead of having fixed naming.
lexs
lexs
Quote:
Original post by niteice
The code needing that variable, most likely, simply checks its value every time it is run, probably every frame. If the code may not be run every frame, Source allows one to attach a callback function to be executed whenever the value changes.


But how does that work perfomance-wise if it gets the value every frame with something like int value = getValue("myName"); ?
Telastyn
Telastyn
Quote:
Original post by lexs
Quote:
Original post by Telastyn
Umm... wow. That violates like 4 of the cardinal no-no's in modern C++ in 3 lines of code (don't use macros, don't use shallow get/setters, don't use bald pointers, don't use class function pointers).

If you're going to go that route then templatizing the param type (the int) would make things easier.


Yeah, i thought i may be some macro abuse, but those aren't shallow get/setters right? Shallow is when they only set the value and do no validation? And my code allows for your own implementation of those get/setters. And about bald pointers, this is only some code i wrote up quickly (there is no implementation yet etc).
Oh, and what's wrong with class function pointers, ie delegates?

I guess you could remove that macro and let the user just pass their own set/get function instead of having fixed naming.


Sure, the getter/setters aren't necessarily shallow, but if you require their implementation you're bound to make them.

The problem with class function pointers is that you're duplicating boost::bind (in a less generic manner), and you're largely assuming that every config value will go to a class method. It would be better to use boost::function for the method and boost::bind for the times when it's not a global method (or has more parameters than a simple setter).

But as I said, it should work; and that's the important thing.
Endar
Endar
Quote:
Original post by Telastyn
Umm... wow. That violates like 4 of the cardinal no-no's in modern C++ in 3 lines of code (don't use macros, don't use shallow get/setters, don't use bald pointers, don't use class function pointers).


What are 'bald' pointers?
What should we be using instead of class function pointers?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Zakwayda
Zakwayda
Quote:
What are 'bald' pointers?
Raw pointers (as opposed to, say, smart pointer objects).
Quote:
What should we be using instead of class function pointers?
Some sort of generalized delegate (e.g. boost::function).
lexs
lexs
Quote:
Original post by Telastyn
Sure, the getter/setters aren't necessarily shallow, but if you require their implementation you're bound to make them.

The problem with class function pointers is that you're duplicating boost::bind (in a less generic manner), and you're largely assuming that every config value will go to a class method. It would be better to use boost::function for the method and boost::bind for the times when it's not a global method (or has more parameters than a simple setter).

But as I said, it should work; and that's the important thing.


Ah, yeah. Actually i would probably use boosts delegates or FastDelegate in the lower level code. And i don't see a reason why a class would be able to specify settings that are stored in another class or even just a function?
Anyways, do you see any drawbacks of this way? How would you do it?
lexs
lexs
Quote:
Original post by jyk
Quote:
Original post by lexs
At the moment i have a Console object which can parse commands etc, to complete it i have a Settings object which allows for easy extraction of variables, like Screen_Width etc. So now i'm thinking about just passing that Settings object to every object that needs it and let them fetch their settings in the constructor.

But Source and another engines allow for on the flow changes to values, how do they do this? Do they attach some sort of "updated()" function to every value? How would you implement something like that?
In my game framework, settings are stored and passed around via reference-counted smart pointer. That way, clients always have access to the current value (which, as in your system, can be modified via the in-game console).


How do you perform validation of values in your system? Say someone sets max_gravity to 1 000 000 when max is 100 000?
Zakwayda
Zakwayda
Quote:
How do you perform validation of values in your system? Say someone sets max_gravity to 1 000 000 when max is 100 000?
The first line of defense is boost::lexical_cast(), which throws an exception if the cast fails. If this exception is thrown, a message is displayed indicating that the value is not a valid value for the setting type. This catches cases such as e.g. trying to assign a value of "hello" to an integer setting.

From there, each setting has an optional validation callback/delegate that is called whenever a value change is requested; if the callback returns false, the value is not changed.

For convenience, I have a number of pre-made 'validator' function objects, such as:

- range minimum
- range maximum
- range minimum and maximum
- positive integer powers of two only
- even only
- odd only
- normalized ([0, 1])

These can be combined together if needed, or, if some other type of validation is required (for example, a 'fullscreen resolution' setting that can only be set to one of the available fullscreen modes), you simply create new function objects as necessary.
niteice
niteice
Quote:
Original post by lexs
Quote:
Original post by niteice
The code needing that variable, most likely, simply checks its value every time it is run, probably every frame. If the code may not be run every frame, Source allows one to attach a callback function to be executed whenever the value changes.


But how does that work perfomance-wise if it gets the value every frame with something like int value = getValue("myName"); ?


I don't have the SDK in front of me to double-check, but I believe they declare a variable which is accessed directly:
x = someVar.getInt();// orx = someVar.intValue;

where someVar is directly entered in the console.
Telastyn
Telastyn
Quote:

How would you do it?


That depends on the game and the language. In my current project (4x game in C#) the configuration file isn't anything more than an auto-loaded script. The file is read in and passed line by line to the quake-style console. That in turn usually sets some values in a configuration class which is passed into a game when started (though the commands can and sometimes does do other things). The console input is the standard interpreter pattern, generated by some canned code that takes arbitrary delegates and generates a nice interpreter with deserialization for them.

A game with different requirements (more complex configuration, variable configuration, more runtime emphasis...) would likely get a different implementation.
lexs
lexs
Quote:
Original post by jyk
Quote:
How do you perform validation of values in your system? Say someone sets max_gravity to 1 000 000 when max is 100 000?
The first line of defense is boost::lexical_cast(), which throws an exception if the cast fails. If this exception is thrown, a message is displayed indicating that the value is not a valid value for the setting type. This catches cases such as e.g. trying to assign a value of "hello" to an integer setting.

From there, each setting has an optional validation callback/delegate that is called whenever a value change is requested; if the callback returns false, the value is not changed.

For convenience, I have a number of pre-made 'validator' function objects, such as:

- range minimum
- range maximum
- range minimum and maximum
- positive integer powers of two only
- even only
- odd only
- normalized ([0, 1])

These can be combined together if needed, or, if some other type of validation is required (for example, a 'fullscreen resolution' setting that can only be set to one of the available fullscreen modes), you simply create new function objects as necessary.


That seems pretty nice, i'll try something like it when i get time. So you basically have some sort of wrapper around a boost::shared_ptr/weak_ptr?

Topic Locked

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

Sign in to reply to this topic.