The Inexor Tree in Inexor Core is actually just a number of exposed variables.
The only difference between a normal variable and a variable shared in the tree is, that upon change, the change gets distributed to all other components which have the same Tree.
The Tree contains no functions, only data.
We use an in-house code generator for building the synchronization code for all SharedVars. We must do this, since C++ has very poor support of [code reflection](
The "InexorGlueGen" code generator gets executed whenever the build folder was deleted or when explicitly triggered by building the target gen_bindings_client
or gen_bindings_server
(i.e. make gen_bindings_client
when using make).
This is necessary when you added, removed or modified SharedVars.
Declaring SharedVars is possible for various types of variables:
- The primitive types
char *
,float
,int
- A std::vector
- Later: std::array, std::deque or std::map
- or any other which can be dropped in for these (e.g. a std::unordered_map)
- Later: std::array, std::deque or std::map
- A class or struct
- Later: A pointer to any of the above
All SharedVars must be in global scope.
When declaring a SharedVar it's path in the Tree is implicitly given following the namespace and the class variables in which it is contained.
Exemplary the variable ::rendering::screen.width
(where rendering
is a namespace in which a variable of a struct-type called screen
is placed, which contains a variable width
) results in the path rendering/screen/width
.
Note:
The namespace inexor
– which is used in C++ as the uppermost namespace for all (non-legacy) code – gets ignored when creating the Tree path.
I.e. the variable ::inexor::rendering::screen.width
will be placed in the Tree as rendering/screen/width
.
The code
namespace rendering {
SHAREDVAR(int, maxfps, Default(200)|Range(0, 1000)|Persistent());
}
creates a SharedVar of type int
named maxfps
.
It gets initialized with the value 200
, each time the variable is set, it is clamped to the range of 0 to 1000, see SharedVarAttributes.
The resulting path of the variable in the Inexor Tree is rendering/maxfps
.
Often a good design tries to encapsulate connected variables in a class or struct.
In the nature of C++ it is clear, that only public
class elements can be synchronized. All SharedVar-elements must be public or get ignored.
namespace inexor {
namespace rendering {
class screen_t
{
public:
SHAREDVAR(int, width, Default(1024)|Range(0, 10000));
};
SHAREDVAR(screen_t, screen, Persistent());
} } // ns inexor::rendering
You can treat the variable as if it is a normal primitive.
For example a SharedVar maxfps
could be used just like a normal variable
int minframetime = 1/maxfps;
However when setting the SharedVar, the change will be synchronized to any other component which has the tree
maxfps = 300;
With SharedVar-Attributes one can attach logic to the variables with minimal effort.
Each SharedVar-Attribute is actually a class definition, hence they get syntax-highlighted correctly in most IDEs.
Using the Operator |
one can attach more than just a single SharedVar-Attribute to a SharedVar.
Although it is easily possible to create SharedVar-Attributes oneself, there are currently four SharedVar-Attributes usable by default.
- Default(value)
- set the default value of the primitive SharedVar
- if no such "Default"-Attribute is given, the default value is
0
,0.0
or""
)
- Range(minimal, maximal)
- clamps the values of the numerical SharedVar (int or float) to the respective range.
- Persistent()
- remember the value of the variable after shutdown of Inexor
- OnChange(functor)
- execute a given code as soon as the variable changes (
Do not use logic as arguments for SharedVar-Attributes!
* e.g. Range(1024, std::min(1200, 1440))
will definitely not work!
* Preprocessor logic is not forbidden though: defines will be correctly replaced before parsing the SharedVar.