Chi-Tech
devman_01_06_StaticRegistration.h
Go to the documentation of this file.
1
/**\page DevManStaticRegistration Static Registration
2
3
\tableofcontents
4
5
This page explains the details of the two utility classes `ParameterBlock` and
6
`InputParameters`.
7
8
\section DevManParametersSec1 1 The general philosophy of Static Registration
9
10
First, what is <B>Static Registration</B>? When a c++ program is executed there
11
are a number of phases that occur in order. Once such phase, before entering the
12
`main` function, is the phase in which all static variables are initialized. We
13
shall call this phase the static initialization phase.
14
In the most basic sense static registration is the process whereby we create a
15
registry of object-constructors during this phase. This registry can then used,
16
during the dynamic phases of the program, to construct objects.
17
A simple example is the code below:
18
\code
19
#include <iostream>
20
#include "DoSomethingFunctions"
21
22
static int a = DoSomething();
23
24
int main()
25
{
26
std::cout << "The value of a is: " << a << std::endl;
27
}
28
\endcode
29
where the variable `int a` is initialized, before the `main` function starts,
30
using a function call, which happens during the static initialization phase of
31
the program.
32
33
\subsection DevManParametersSec1_1 Rigging static initialization for object factories or object makers
34
35
We can use the rather odd dynamics of singleton objects to create a static
36
registry of function creation calls during static initialization. In the code
37
below we define an `ObjectMaker` class that is a singleton. A singleton is an
38
object of which only a single instance can be created, which we do by declaring
39
a static instance, a bunch of static members methods, a private constructor and
40
deleted copy- and move constructors.
41
42
The `ObjectMaker` maintains a map of string keys to object construction calls.
43
The construction calls always return a pointer to a base class `Object` but can
44
construct any child object by using the shown template arguments. In order for
45
us to achieve basic static registration we need two templated-methods, these
46
are `CallObjectConstructor` and `RegisterObject` in the code below.
47
The first method defines a function-address that we can put into our registry.
48
The second method is the actual method we call to make a registry entry. When we
49
then want to make a registered object we simply call the `MakeRegisteredObject`
50
method.
51
52
\code
53
#include <iostream>
54
#include <map>
55
#include <functional>
56
57
class Object
58
{
59
public:
60
int member_;
61
Object() : member_(99) {}
62
Object(int member) : member_(member) {}
63
};
64
65
class ChildObject : public Object
66
{
67
public:
68
ChildObject() : Object(999) {}
69
};
70
71
//===============================================
72
typedef std::function<Object*()> CreationFunction;
73
class ObjectMaker // Singleton
74
{
75
public:
76
std::map<std::string, CreationFunction> registry_;
77
78
public:
79
static ObjectMaker& GetInstance() noexcept
80
{
81
static ObjectMaker singleton;
82
return singleton;
83
}
84
template <typename T>
85
static Object* CallObjectConstructor() { return new T(); }
86
87
template <typename T>
88
static int RegisterObject(const std::string& register_name)
89
{
90
auto& instance = GetInstance();
91
instance.registry_.insert(
92
std::make_pair(register_name, &CallObjectConstructor<T>));
93
return 0;
94
}
95
96
static Object* MakeRegisteredObject(const std::string& obj_name)
97
{
98
auto& instance = GetInstance();
99
100
return instance.registry_.at(obj_name)();
101
}
102
private:
103
ObjectMaker() = default;
104
ObjectMaker(const ObjectMaker&) = delete;
105
ObjectMaker(const ObjectMaker&&) = delete;
106
ObjectMaker& operator=(const ObjectMaker&) = delete;
107
};
108
109
//===============================================
110
// This is where we statically register an object
111
static int abc12345 = ObjectMaker::RegisterObject<Object>("Parent");
112
static int abc12346 = ObjectMaker::RegisterObject<ChildObject>("Child");
113
114
115
int main()
116
{
117
Object objectA(98); // Conventional object creation
118
auto objectB = ObjectMaker::MakeRegisteredObject("Parent");
119
auto objectC = ObjectMaker::MakeRegisteredObject("Child");
120
121
std::cout << "Object A.member_ = " << objectA.member_ << "\n";
122
std::cout << "Object B.member_ = " << objectB->member_ << "\n";
123
std::cout << "Object C.member_ = " << objectC->member_ << "\n";
124
125
return 0;
126
}
127
\endcode
128
Output:
129
\verbatim
130
Object A.member_ = 98
131
Object B.member_ = 99
132
Object C.member_ = 999
133
\endverbatim
134
135
Notice that we abuse the static initialization phase right above the `main`
136
function. We call the static singleton `ObjectMaker` and `RegisterObject` to
137
create registry entries for a `Parent` and a `Child`. In the main function we
138
then construct some of these statically registered objects.
139
140
\section DevManParametersSec2 2 Implementation in ChiTech: ChiObject and ChiObjectFactory
141
In ChiTech we have to, of course, enhance the functionality by a lot, to this
142
end we have the base object, `ChiObject`, and the factory, `ChiObjectFactory`.
143
Also, instead of using raw pointers, we use shared pointers for all
144
objects that can be statically registered.
145
146
\image html "DeveloperManual/StaticRegistration/StaticRegistration.png" "The factory pattern" width=900px
147
148
We developed a separate system to
149
control object construction parameters (more on this later). Additionally,
150
we do not want developers to come up with unique dummy variable names for the
151
static registration, i.e.,
152
`static int abc12345` or `static int abc12346`, just to make use of the
153
`ChiObjectFactory`, therefore, we use a macro. That works as follows.
154
155
In order to register an object within the `ChiObjectFactory` we created a macro
156
called `RegisterChiObject(namespace_name, obj_name)`. The arguments to this
157
macro, `namespace_name` and `obj_name`, then get used to construct the following
158
\code
159
RegisterChiObject(namespace_name, ObjectName);
160
//
161
// becomes:
162
//
163
static char unique_var_name_object_ObjectName_0 =
164
ChiObjectFactory::AddObjectToRegistry<ObjectName, ChiObject>("namespace_name",
165
"ObjectName")
166
\endcode
167
which allows us to abuse static initialization to register the object with
168
text-name `"namespace_name::ObjectName"`. Note that nested namespaces are
169
allowed, e.g., `"space1::space1b::space1bc"`, is allowed.
170
171
We also implemented a number of "tricks". For example we created a
172
behind-the-scenes connection to the lua console so that registered objects can
173
be created from the lua input system. We also created a "RegistryDump" where
174
each registered object can dump its input arguments specification so we can use
175
it to build documentation (lessening the burden on the developer).
176
177
\section DevManParametersSec3 3 The InputParameters system
178
All statically registered objects have a common input parameters interface
179
implemented in the general class `chi::InputParameters`. This allows
180
us to fully support static registration if an object has the following basic
181
structure
182
\code
183
#include "ChiObject/chi_object.h"
184
185
namespace zorba
186
{
187
188
class CoolObject : public ChiObject
189
{
190
const int a_;
191
const double b_;
192
//Misc. members
193
public:
194
static chi::InputParameters GetInputParameters();
195
CoolObject(const chi::InputParameters& params);
196
197
//Misc. methods
198
};
199
200
}
201
\endcode
202
Here we have the static member method `GetInputParameters` which returns a
203
`chi::InputParameters` object used to assign parameters from user input,
204
and the constructor `CoolObject(const chi::InputParameters& params);`
205
which uses such an object.
206
207
In the definition (`.cc`) file we then have
208
\code
209
#include "CoolObject.h"
210
211
#include "ChiObject/object_maker.h"
212
213
namespace zorba
214
{
215
216
RegisterChiObject(chi_unit_tests, CoolObject);
217
218
chi::InputParameters CoolObject::GetInputParameters()
219
{
220
chi::InputParameters params = ChiObject::GetInputParameters();
221
222
params.SetGeneralDescription("A Cool object to play with");
223
params.SetDocGroup("doc_Coolstuff"); // Documentation link
224
225
params.AddRequiredParameter<int>("a", "Coolness factor a");
226
params.AddOptionalParameter("b", 1.0, "Coolness factor b");
227
228
return params;
229
}
230
231
CoolObject::CoolObject(const chi::InputParameters& params) :
232
ChiObject(params),
233
a_(params.GetParamValue<int>("a")),
234
b_(params.GetParamValue<double>("b"))
235
{
236
}
237
238
}//namespace zorba
239
\endcode
240
241
Creating this object from lua is then as easy as
242
\code
243
params = {a = 98, b = 99.0}
244
cool_obj = zorba.CoolObject.Create(params)
245
\endcode
246
247
\subsection DevManParametersSecFAQ1 FAQ1 Documentation-strings (Doc-strings)
248
All parameters must have a documentation string (short name "doc string").
249
For example:
250
\code
251
params.AddRequiredParameter<int>("a", "Coolness factor a");
252
params.AddOptionalParameter("b", 1.0, "Coolness factor b");
253
\endcode
254
here "Coolness factor a" and "Coolness factor b" are the doc strings for
255
parameters "a" and "b" respectively.
256
257
You can also add documentation for the entire object. When the documentation
258
is auto-generated this description will appear at the top.
259
\code
260
params.SetGeneralDescription("General test object");
261
\endcode
262
263
By default the object's documentation will be posted to a group that is
264
reference-able via doxygen as `<namespace_name>__<ObjectName>` (note the double
265
underscore is a replacement for the namespace scope clarifiers `::`). You can
266
link the documentation to other groups by using the `SetDocGroup()`
267
method. This functionality allows you to customize your documentation according to your
268
needs.
269
270
<B>Note:</B> The block of text supplied to `SetGeneralDescription` is pasted
271
into a documentation page that will be processed by doxygen so all doxygen
272
commands will work here (including formulas, code blocks, and links).
273
274
\subsection DevManParametersSecFAQ2 FAQ2 Optional Parameters
275
Optional parameters must always have a default value. However, you do not
276
necessarily have to use it. In cases where you use a parameter only when
277
supplied,
278
the `InputParameters` object has the interface method `ParametersAtAssignment`
279
which returns the parameters used at assignment. It is used as follows:
280
\code
281
CoolObject::CoolObject(const chi::InputParameters& params) :
282
ChiObject(params),
283
a_(params.GetParamValue<int>("a")),
284
b_(0.0)
285
{
286
const auto& user_params = params.ParametersAtAssignment();
287
if (user_params.Has("b"))
288
b_ = user_params.GetParamValue<double>("b");
289
}
290
\endcode
291
292
\subsection DevManParametersSecFAQ3 FAQ3 Inheriting parameters from parent objects
293
Input parameters can be inherited from parents in the `GetInputParameters`
294
method call. For example
295
\code
296
chi::InputParameters LBSSolver::GetInputParameters()
297
{
298
chi::InputParameters params =
299
chi_physics::Solver::GetInputParameters();
300
params += chi::SecondParent::GetInputParameters();
301
//
302
//etc.
303
\endcode
304
Gets all the parameters from the parent object `chi_physics::Solver` and then
305
`chi::SecondParent`, allowing us to change or add to these parameters.
306
307
In fact, a pretty slick move here is that we are not bound by classical
308
inheritance. The input parameters of any statically registered object can be
309
snatched and used by simply calling its static `GetInputParameters` method.
310
311
\subsection DevManParametersSecFAQ4 FAQ4 Changing inherited options to optional/required
312
If a required parameter is to be changed to optional we can do this using the
313
interface `ChangeExistingParamToOptional`, e.g.,
314
\code
315
chi::InputParameters LBSSolver::GetInputParameters()
316
{
317
chi::InputParameters params =
318
chi_physics::Solver::GetInputParameters();
319
320
params.ChangeExistingParamToOptional("name", "LBSDatablock");
321
\endcode
322
323
Vice versa there is also `ChangeExistingParamToRequired<type>("name")`.
324
325
\subsection DevManParametersSecFAQ5 FAQ5 Deprecated parameters
326
Parameters in an `InputParameters` block can be marked as deprecated via 3
327
different mechanisms:
328
- Marking them with a deprecation warning `MarkParamaterDeprecatedWarning`. This
329
will generate just a warning if the parameter is supplied..
330
- Marking them with a deprecation error `MarkParamaterDeprecatedError`. This
331
will generate an error if the parameter is supplied..
332
- Marking them with as renamed `MarkParamaterRenamed`. This will generate an
333
error if the parameter is supplied if the parameter is supplied.
334
335
\subsection DevManParametersSecFAQ6 FAQ6 Constrained ranges
336
Parameters in an input parameters block can be constrained to the following
337
- `chi_data_types::AllowableRangeList`, created with a `std::vector` of a
338
specific type. The parameter can then only be one of those types.
339
- `chi_data_types::AllowableRangeLowLimit`. Parameter must be >= than the
340
value specified when the range is specified as closed or just > that the value
341
specified when the range is specified as open.
342
- `chi_data_types::AllowableRangeHighLimit` same as the low limit but just with
343
a smaller than comparison.
344
- `chi_data_types::AllowableRangeLowHighLimit` combination of both a high and
345
a low limit range.
346
347
Example:
348
\code
349
chi::InputParameters StrangeSolver::GetInputParameters()
350
{
351
chi::InputParameters params =
352
chi_physics::Solver::GetInputParameters();
353
params.AddRequiredParameter<std::string>(
354
"sdm_type", "The spatial discretization type to be used");
355
356
using namespace chi_data_types;
357
params.ConstrainParameterRange(
358
"sdm_type", AllowableRangeList::New({"FV", "PWLC", "PWLD"}));
359
// etc.
360
\endcode
361
*/
doc
PAGES
ProgrammersManual
devman_01_06_StaticRegistration.h
Generated by
1.9.3