woo::Object¶
All objects in Woo derive from the woo.core.Object
(woo::Object
) class. This base object automatically provides, together with some additional scaffolding described below, basic services:
Serialization and deserialization – all objects can be saved and loaded again.
Information for showing each object in the GUI via attribute traits.
Exposing c++ classes to Python via boost::python.
Elegant type-casting and RTTI methods (
cast
andisA
templates).Some introspection capabilities, used mainly for dispatching (class index), and fully available from Python.
Guarantess that all attributes are initialized (as long as they provide an initialization value
WOO_DECL__
and WOO_IMPL__
macro family¶
The following macros are the standard (and the only supported) way to declare an objects deriving from woo::Object
. The complexity is a necessary compromise for two contradicting requirements:
Have all the important data in one place, for programmer’s convenience. Not half in header and half in implementation file.
Put only as little data as possible into the header file, so that compilation only
#include
’ing header is not slowed down and takes less RAM (boost::python’s templates are rather memory-hungry).
Therefore, a macro is defined, which contains all the data necessary, and is passed to WOO_DECL__
in the header and WOO_IMPL__
in the implementation file.
The macros is conventionally named as e.g. woo_zoo_Rabbit__...
, where zoo
is module (e.g. core
, dem
or other). The ...
describe the comma-separated argument types in the macro:
woo_zoo_Rabbit__CLASS_BASE_DOC
for class name, base class name, and class docstring (exposed in Python); these three are mandatory, additional fields are added as needed,woo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS
adds attribute specifications (described below)woo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS_PY
adds python initialization blockwoo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS_CTOR_PY
allows to insert things into the default constructor (rarely needed)woo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS_INI_CTOR_PY
allows additionally to specify initializers for other variables (seldom needed)
Note that the arguments must be separated by literal comma (,
), and no other free commas (not enclosed by parentheses ()
) are allowed (this is a limitation of the C preprocessor).
A simple class declarations may look like this in the hypothetical pkg/zoo/Animal.hpp
file:
struct Animal: public Object{
#define woo_zoo_Animal__CLASS_BASE_DOC \
Animal,Object,"Any animal"
WOO_DECL__CLASS_BASE_DOC_ATTRS(woo_zoo_Animal__CLASS_BASE_DOC);
};
WOO_REGISTER_OBJECT(Animal); // must be out of the body
struct Rabbit: public Animal{
/* macro is defined on multiple lines, joined by trailing backslash */
#define woo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS_PY \
Rabbit,Animal,"This is a rabbit class, blah blah.", \
((int,legs,4,,"Number of legs")) \
((Real,age,0,,"Age of the rabbit"))
WOO_DECL__CLASS_BASE_DOC_ATTRS_PY(woo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS_PY);
};
WOO_REGISTER_OBJECT(Rabbit);
And in the .cpp
file:
#include<woo/pkg/zoo/Animal.hpp>
// all exported classes; may be used several times in the same file with different modules/classes
WOO_PLUGIN(zoo,(Animal)(Rabbit));
WOO_IMPL__CLASS_BASE_DOC_ATTRS(woo_zoo_Animal__CLASS_BASE_DOC);
WOO_IMPL__CLASS_BASE_DOC_ATTRS(woo_zoo_Rabbit__CLASS_BASE_DOC_ATTRS);
Other macros¶
There are two more macros which must be used with every new class:
WOO_REGISTER_OBJECT(Rabbit);
in the header file, after the class declaration and outside of the class body; this is necessary for proper support ofboost::serialization
;WOO_PLUGIN(zoo,(Animal)(Rabbit));
in the implementation file; internally, this macro puts the classes to the given module (woo.zoo
in this example), and completes the serialization support. It may appear more than once in one implementation file (though not with the same classes).
Attributes¶
The ATTR
part of the WOO_DECL__
macros is a list of ((
and ))
enclosed 5-tuples describing each attribute (memeber variable, in strict c++ terminology). The components of the tuple are as follows:
c++ type of the attribute; it is subject to the following:
The type must not contain comma due to macro processing; if you need something like
std::map<int,int>
, usetypedef
inside the class body to declare a shorthand for your type which does not contain one.Unless the attribute is declared with
Attr::noSave
(see below), it must be type whichboost::serialization
knows how to serialize. Most useful types (int
,Real
, Eigen vectors and matrices,std::vector
,std::map
,boost::multiarray
,shared_ptr
to anything deriving fromwoo::Object
, … are handled just fine)The type should have a converter for python (unless declared with
Attr::hidden
, see below); if it does not have converter, compilation will succeed, but accessing the attribute from python will raiseTypeError
.
Attribute name; must be a valid c++ (and python) identifier. The name is, of course, case-sensitive.
Default value; if not given (blank), the attribute will be default-initialized. It is strongly recommended to initialize all attributes, to catch any possible problems coming from initialization not being done.
Attribute traits (discussed below)
Docstring for the attribute, which will show up in the automatically generated documentation, is also accessible with
?
from IPython prompt, and is shown as tooltip in the UI. Use Sphinx formatting.
Attribute traits¶
Attribute traits is piece of information statically attached in to every attribute; that means, all instances of the attribute share the trait. The trait is defined e.g. in the following way:
AttrTrait<Attr::noSave|Attr::triggerPostLoad>().noGui().pyByRef()
The Attr::noSave|Attr::triggerPostLoad
is template parameter neede for compile-time switches. If there are no flags, the template still must be spelled out, such as in AttrTrait<>().noGui()
. The trailing methods, which can be chained as they return reference to the attribute trait instance, contain information which is not used until runtime. Traits are fully described by their source in lib/object/AttrTrait.hpp. An overview of those which are used the most follows.
Compile-time flags¶
These flags are given as template argument to AttrTrait<…>(), combined with the |
(bit-wise OR) operator if needed.
Attr::noSave
: the attribute will not be saved, when saving viaboost::serialization
. After loading a saved instance, it will keep the default value. This is useful for types not supported byboost::serialization
or for saving the amount of data saved if they are redundant and can be reconstructed after the class is loaded.Attr::triggerPostLoad
: when the attribute is modified from Python, thepostLoad(...)
function is called, with the address of the member variable passed as the second argument (see woo::Object::postLoad).Attr::hidden
: attribute not exposed to Python at all.Attr::namedEnum
: signals that the attribute will be exposed as named enumeration (more on that below); this must be complemented by.namedEnum(...)
specified on the trait, defining which are the admissible values and their names.
Runtime traits¶
.noDump()
: do not dump this attribute when serializing to formats withoutboost::serialization
formats, such as JSON, Python expression, Pickle, HTML and others. Useful to avoid large nested structures inflating those representations with useless data..pyByRef()
: expose the attribute by reference to Python. The default is to expose by-value, with the exception ofEigen
’s matrices and vectors (seepy_wrap_ref
template in lib/object/Object.hpp).shared_ptr
exposed by-value in reality exposes reference to the object. This attribute is only rarely useful..readonly()
: the attribute is no writable from python; this includes passing the value to the constructor..namedEnum({{1,{"one","eins","just one"}},{0,{"zero","null","nothing"}}})
: in conjunction with theAttr::namedEnum
flag described above, define string aliases for integral values, using initializer list. The first string is the preferred name (always returned) while the other ones are aliases, which can be also used. Assigning the integral value is still possible.
THe following traits influence how is the attribute displayed in the GUI:
.noGui()
: do not show this attribute in the GUI, even though it is exposed to Python..rgbColor()
: used forVector3r
attributes with the meaning of color; color picker icon will be displayed in the UI, for picking color visually..filename()
,.existingFilename()
,.dirname()
: in the UI, show picker for (possibly non-existing) filename, existing filename, and directory, respectively..angleUnit()
,.timeUnit()
,.lenUnit()
,.areaUnit()
,.volUnit()
,.velUnit()
,.accelUnit()
,.massUnit()
,.angVelUnit()
,.angMomUnit()
,.inertiaUnit()
,.forceUnit()
,.torqueUnit()
,.pressureUnit()
,.stiffnessUnit()
,.massRateUnit()
,.densityUnit()
,.fractionUnit()
,.surfEnergyUnit()
: self-explanatory unit specification for given attribute. The value is always internally stored in base unit (usually the SI), but the UI may scale the quantity and present it in a readable way. The units are accessible from thewoo.unit
dicionary, e.g. aswoo.unit['t/h']
, which contains the multiplier..multiUnit()
: for 2d arrays (such as list ofVector2r
), use different units for every column; this must be followed by unit traits; e.g. PSD points declared asvector<Vector2r>
may say.multiUnit().lenUnit().fractionUnit()
..range(Vector2i(a,b))
,.range(Vector2r(a,b))
: show UI slider for integral/float values betweena
andb
..choice({1,2,3})
: UI choice from integral values.bits({"bit 0","bit 1"})
: create checkboxes for individual bits of an integer attribute (starting from the least significant, i.e. the rightmost bit).hideIf("self.foo=='bar'")
: the attribute will be dynamically hidden from the UI if the expression given evaluates toTrue
;self
is the current instance..startGroup("Name")
: start attribute group namedName
, shown as collapsible group of attributes, and also shown in the documentation as an attribute group..buttons({"button label","python command to be executed","label",...})
: create button(s) in the UI, where each triplet specifies one button; it will be created after the attribute itself by default, which can be changed by setting the second optional argumentshowBefore
totrue
.
Serialization support¶
Every Woo object declared with the WOO_DECL__
and WOO_IMPL__
macros introduced above automatically has the ability to be serialized and deserialized (saved and loaded). C++ objects are natively (fully) supported by boost::serialization, with the ability to save to binary and XML formats, including optional compression using gzip or bzip2. The serialization routines are implemented in lib/object/ObjectIO.hpp. boost::serialization
takes care of object tracking (when several objects point to another object via shared_ptr
, it is saved only once, and the pointers are correctly updated when deserialized) and many other tricky aspects of serialization. Most attribute types are supported for serialization, including shared_ptr
to any woo::Object
or its subclasses, Eigen::Matrix
(any subclasses), primitive datatypes (numbers, strings), STL containers (std::vector
, std::list
, std::map
of any of the above), boost::python::object
(internally using the cPickle module) and so on.
When loading (deserializing), boost::serialization
default-constucts the type, then sets it attributes. For this reason, all Woo objects must be default-constructible. This is taken care of by the woo::Object
macros.
C++ objects can also be (de)serialized using Python, in which case not all information is stored. The documentation refers to this process as dumping (rather than serializing), where the formats supported are python expression (evaluating to the object, when read back), HTML dump, JSON. These are meant for post-processing rather than storing and reloading bigger objects at once.
There are four methods called around (de)serialization, if defined (they are found via ADL; that’s why the first argument is reference to the current instance, which is no really useful in the code)
preSave(Rabbit&)
, before saving an object; useful for converting internal data not handled by boost::serialization into some better format suitable for saving, or otherwise preparing the object for serialization; seldom used.postSave(Rabbit&)
, after saving an object; seldom used.preLoad(Rabbit&)
, before loading an object; seldom used.postLoad(Rabbit&,void* attr=NULL)
, after loading an object. Theattr
pointer isNULL
when called after deserialization (other cases are discussed below).
woo::Object::postLoad¶
The postLoad
function serves two purposes, depending on the value of the second void* attr
argument:
with
attr==NULL
, the object is asked to get set up after having been deserialized.with
attr!=NULL
, the pointer hold address of an attribute which has been changed from Python (if it was declared withAttr::triggerPostLoad
) and can react accordingly, e.g.update dependent attributes: for instance,
woo.dem.ConveyorInlet
handles a number of inter-dependent options, such asvel
,massRate
and others; when one of them is set, the other ones are recomputed to be consistent;values can be checked for validity; if invalid, exception is thrown from
postLoad
, letting the user know about invalid value immediately;unit vector attributes can be automatically normalized in
postLoad
when set.
Casting¶
woo::Object
defines two utility methods for casting:
object->isA<Type>()
dynamically checks type ofobject
, such as:if(shape->isA<Sphere>()) /* ... */;
object->cast<Type>()
statically castsobject
toType
; if the cast is invalid, the behavior is undefined (most likely a crash), this has to be assured by context or byisA
check before:if(shape->isA<Sphere>()) radius=shape->cast<Sphere>().radius``.
Tip
Report issues or inclarities to github.