Introspection#
SAMSON Extensions expose their functionality to SAMSON and to other SAMSON Extensions thanks to an introspection mechanism. Exposition is achieved thanks to descriptor files that contain the list of exposed classes and functions, and that are compiled along with the SAMSON Extension. One of the major benefits of the introspection mechanism is the ability to share and combine functionality without the need for providing any type of source code or header code. It is thanks to this introspection mechanism that SAMSON Extensions form more than a collection of independent modules.
Exposing the functionality of a SAMSON Extension typically involves two main steps:
- Writing one descriptor per class that should be exposed
- Writing one descriptor for the SAMSON Extension
The SAMSON Extension Generator automatically generates descriptor files that expose the default functionality of the classes it generates. These descriptors may be modified as indicated below in order to expose custom functionality added to the generated classes.
Exposing classes#
Registering classes for exposition#
Exposing a class to SAMSON first involves three steps:
- Adding the
SB_CLASS
macro to the declaration of the class - Registering the class with SAMSON's type system using the
SB_REGISTER_TARGET_TYPE
orSB_REGISTER_TYPE
macros - Registering a potential derivation relationship with SAMSON's type system using the
SB_DECLARE_BASE_TYPE
macro
The following App header code, generated by the SAMSON Extension Generator (in a file called SEElementMyApp.hpp
), demonstrates these three steps:
class SEElementMyApp : public SBDApp {
SB_CLASS
public:
/// \name Constructors and destructors
//@{
SEElementMyApp(); ///< Constructs an app
virtual ~SEElementMyApp(); ///< Destructs the app
//@}
/// \name GUI
//@{
SEElementMyAppGUI* getGUI() const; ///< Returns a pointer to the app GUI
//@}
};
SB_REGISTER_TARGET_TYPE(SEElementMyApp, "SEElementMyApp",
"D5A1BA95-9CD4-3424-9713-06B979F5C0D9");
SB_DECLARE_BASE_TYPE(SEElementMyApp, SBDApp);
The macros should be added in specific positions:
- The
SB_CLASS
macro should be added inside the class declaration. - The
SB_REGISTER_TARGET_TYPE
macro should be added after the class declaration. - The
SB_DECLARE_BASE_TYPE
macro should be added after the class has been registered withSB_REGISTER_TARGET_TYPE
.
The SB_REGISTER_TARGET_TYPE
macro has the following syntax:
- The first parameter (without quotes) is the type that should be registered
- The second parameter (in quotes) is the short name of the class. This should be a single word with a syntax typical of class names. If a short name is not needed (e.g. because the original class name is short enough), then the original class name should be used.
- The third parameter (in quotes) is the class's unique identifier, which could be generated thanks to the provided UUID Generator.
The SB_DECLARE_BASE_TYPE
macro has the following syntax:
- The first parameter is the derived type.
- The second parameter is the base type. This class must be exposed as well (this is the case for base classes provided in the SAMSON SDK, such as the SBDApp class in the example above).
Note that, at the moment, SAMSON's introspection mechanism may not expose multiple inheritance relationships: while it is perfectly possible to expose a class that inherits multiple classes, the SB_DECLARE_BASE_TYPE
macro may be used to expose only one derivation relationship, with only one of the base classes.
Writing class descriptors#
Once a class is registered and its potential derivation relationship is declared using the macros described in the previous section, a corresponding class descriptor may be written.
Class descriptor structure#
A class descriptor implements a proxy for the class, and contains five main descriptions:
- A type description
- A class description
- A class version number
- A factory description, i.e. a list of constructors descriptions
- An interface description, i.e. a list of member functions descriptions
Descriptions are written using macros provided by the SAMSON SDK. In particular, a class descriptor is enclosed between two macros, SB_CLASS_BEGIN
and SB_CLASS_END
, that take as argument the class name (e.g. SB_CLASS_BEGIN
(SEElementMyApp) and SB_CLASS_END
(SEElementMyApp) for the example above).
The example below shows a descriptor generated by the SAMSON Extension Generator (in a file called SEElementMyAppDescriptor.hpp) for the App class it has generated:
SB_CLASS_BEGIN(SEElementMyApp);
SB_CLASS_TYPE(SBCClass::App);
SB_CLASS_DESCRIPTION("SAMSON Extension generator pro tip: modify me");
SB_CLASS_VERSION_NUMBER("1.0.0");
SB_FACTORY_BEGIN;
SB_CONSTRUCTOR_0(SEElementMyApp);
SB_FACTORY_END;
SB_INTERFACE_BEGIN;
SB_INTERFACE_END;
SB_CLASS_END(SEElementMyApp);
Type description#
The class type is added to the class descriptor thanks to the SB_CLASS_TYPE
macro. The argument of the macro should be one of the possible class types.
Class description#
The class description is added to the class descriptor thanks to the SB_CLASS_DESCRIPTION
macro. The argument of the macro should be a short string.
Class version number#
The class version number is added to the class descriptor thanks to the SB_CLASS_VERSION_NUMBER
macro. The argument of the macro should be a string corresponding to a three-part version number: "major.minor.patch", which should be increased in particular when serialization of the class changes.
Factory description#
The class factory, i.e. the collection of class constructors, is added to the class descriptor between the SB_FACTORY_BEGIN
and SB_FACTORY_END
macros:
Each constructor should be added using one of the seventeen provided macros SB_CONSTRUCTOR_0
, SB_CONSTRUCTOR_1
, ... SB_CONSTRUCTOR_16
, where the integer indicates the number of arguments taken by the constructor.
For example, the class constructor
may be exposed with the following macro:
If multiple constructors are exposed, they are all described in a unique pair of enclosing macros SB_FACTORY_BEGIN
and SB_FACTORY_END
, e.g.:
SB_FACTORY_BEGIN;
SB_CONSTRUCTOR_0(SEElementMyApp);
SB_CONSTRUCTOR_1(SEElementMyApp, const std::string&);
SB_CONSTRUCTOR_1(SEElementMyApp, int);
SB_FACTORY_END;
Note that one constructor that takes no argument should always exist and be exposed.
Interface description#
The class interface, i.e. the collection of class functions, is added to the class descriptor between the SB_INTERFACE_BEGIN
and SB_INTERFACE_END
macros.
For example, the member function
may be exposed with the following macro inserted between the SB_INTERFACE_BEGIN
and SB_INTERFACE_END
macros:
If a function is const
:
then it may be exposed with a macro for const functions:
Finally, if a function is static
:
then it may be exposed with a macro for static functions:
In general, each function should be added using one of the fifty-one provided macros:
SB_FUNCTION_0
,SB_FUNCTION_1
, ...SB_FUNCTION_16
for regular (i.e. non-const, non-static) functions, where the integer indicates the number of arguments taken by the function.SB_CONST_FUNCTION_0
,SB_CONST_FUNCTION_1
, ...SB_CONST_FUNCTION_16
for const functions, where the integer indicates the number of arguments taken by the const function.SB_STATIC_FUNCTION_0
,SB_STATIC_FUNCTION_1
, ...SB_STATIC_FUNCTION_16
for static functions, where the integer indicates the number of arguments taken by the static function.
If multiple functions are exposed, they are all described in a unique pair of enclosing macros SB_INTERFACE_BEGIN
and SB_INTERFACE_END
, e.g.:
SB_INTERFACE_BEGIN;
SB_CONST_FUNCTION_0(std::string, SEElementMyApp, getName);
SB_FUNCTION_1(void, SEElementMyApp, setName, const std::string&);
SB_CONST_FUNCTION_2(SBQuantity::length, SEElementMyApp, getDistance,
const SBAtom*, const SBAtom*);
SB_STATIC_FUNCTION_0(int, SEElementMyApp, getNumberOfInstances);
SB_INTERFACE_END;
Note that even when no functions are exposed, the class descriptor should always contain a class interface:
Finally, note that virtual functions that are part of the API of SAMSON (i.e. are member functions of classes defined in the SAMSON SDK), and that are reimplemented in derived classes in SAMSON Extensions, do not have to be exposed: since the function is defined in the SAMSON SDK, developers may write code that calls the function without relying on the introspection mechanism, and the appropriate implementation will be called at runtime.
Attributes description for the Inspector#
If your class can be accessed through the Inspector, you can expose the class functionality using the following macroses from the class interface:
SB_ATTRIBUTE_READ_ONLY
- expose a read-only attribute.SB_ATTRIBUTE_READ_WRITE
- expose a changeable attribute.SB_ATTRIBUTE_READ_WRITE_CLEAR
- expose a changeable attribute that can be cleared.SB_ATTRIBUTE_READ_WRITE_CLEAR_ARRAY
- expose a changeable array that can be cleared.SB_ATTRIBUTE_READ_WRITE_RANGE
- expose a changeable attribute that has minimum and maximum values defined.SB_ATTRIBUTE_READ_WRITE_RANGE_SLIDER
- expose a changeable attribute that has minimum and maximum values defined.SB_ATTRIBUTE_READ_WRITE_LIST
- expose a changeable attribute that can be selected from a list.
These macroses will also expose the associated to an attribute getter, setter, and other functions depending on a macros.
Exposing a SAMSON Extension#
Once descriptors have been written for all classes that should be exposed, a descriptor may be written for the SAMSON Extension itself.
Extension descriptor structure#
A SAMSON Extension descriptor contains five parts:
- A description
- A documentation address
- A version number
- A list of exposed classes
- A list of categories
Again, descriptions are written using macros provided by the SAMSON SDK. Typically, this descriptor is generator by the SAMSON Extension Generator, and needs to be completed when more classes are added to the SAMSON Extension:
SB_ELEMENT_DESCRIPTION("This SAMSON Extension contains an App that shakes atoms.");
SB_ELEMENT_DOCUMENTATION("Resource/Documentation/doc.html");
SB_ELEMENT_VERSION_NUMBER("1.0.0");
SB_ELEMENT_CLASSES_BEGIN;
SB_ELEMENT_CLASS(SEElementMyApp);
SB_ELEMENT_CLASSES_END;
SB_ELEMENT_CATEGORIES_BEGIN;
SB_ELEMENT_CATEGORY(SBClass::Category::General);
SB_ELEMENT_CATEGORIES_END;
Description#
The description is added to the SAMSON Extension descriptor thanks to the SB_ELEMENT_DESCRIPTION
macro. The argument of the macro should be a short string.
Documentation address#
The address of the SAMSON Extension documentation is added to the SAMSON Extension descriptor thanks to the SB_ELEMENT_DOCUMENTATION
macro. The argument of the macro should be a short string containing the address relative to the installation folder of the SAMSON Extension (typically in the generated Resource/Documentation/
folder).
Version number#
The version number of the SAMSON Extension is added to the SAMSON Extension descriptor thanks to the SB_ELEMENT_VERSION_NUMBER
macro. The argument of the macro should be a short string containing a well-formated version number. Note that the version number of the SAMSON Extension is completely independent of the version number of the SAMSON SDK used to build the SAMSON Extension. Please refer to the chapter about versioning for more information.
List of exposed classes#
The list of exposed classes is added to the SAMSON Extension descriptor between the SB_ELEMENT_CLASSES_BEGIN
and SB_ELEMENT_CLASSES_END
macros:
Each exposed class (i.e. each class which has a descriptor) should be added using the SB_ELEMENT_CLASS
macro.
If multiple classes are exposed, they are all described in a unique pair of enclosing macros SB_ELEMENT_CLASSES_BEGIN
and SB_ELEMENT_CLASSES_END
, e.g.:
SB_ELEMENT_CLASSES_BEGIN;
SB_ELEMENT_CLASS(SEElementMyApp);
SB_ELEMENT_CLASS(SEElementMyVisualModel);
SB_ELEMENT_CLASS(SEElementMyFirstInteractionModel);
SB_ELEMENT_CLASS(SEElementMySecondInteractionModel);
SB_ELEMENT_CLASSES_END;
List of categories#
The list of SAMSON Extension categories is added to the SAMSON Extension descriptor between the SB_ELEMENT_CATEGORIES_BEGIN
and SB_ELEMENT_CATEGORIES_END
macros:
SB_ELEMENT_CATEGORIES_BEGIN;
SB_ELEMENT_CATEGORY(SBClass::Category::General);
SB_ELEMENT_CATEGORIES_END;
Each category should be added using the SB_ELEMENT_CATEGORY
macro, which takes as argument a category.
If the SAMSON Extension should belong to multiple categories, they are all listed in a unique pair of enclosing macros SB_ELEMENT_CATEGORIES_BEGIN
and SB_ELEMENT_CATEGORIES_END
, e.g.:
SB_ELEMENT_CATEGORIES_BEGIN;
SB_ELEMENT_CATEGORY(SBClass::Category::General);
SB_ELEMENT_CATEGORY(SBClass::Category::Medicine);
SB_ELEMENT_CATEGORY(SBClass::Category::Biology);
SB_ELEMENT_CATEGORIES_END;
Using exposed classes#
Once a class is exposed, SAMSON and other SAMSON Extensions may create instances of it and call its member functions. This section explains how.
Class proxies#
The functionality of an exposed class is accessible through its class proxy, which gathers the class factory (its collection of constructors) and the class interface (its collection of member functions).
The class proxy of an exposed class is retrieved using SAMSON:
In case there is a risk that several SAMSON Extensions might provide a class called SEElementMyApp
, it is possible to specify the name and the UUID of the SAMSON Extension:
SBProxy* classProxy = SAMSON::getProxy("SEElementMyApp",
SBUUID("C8EC88EA-38CE-70F4-2A74-C71F2C86A692"));
The SAMSON Extension UUID is guaranteed to be unique if the SAMSON Extension has been installed from SAMSON Connect.
Constructing class instances#
The creation of a class instance depends on the type of the exposed class.
Special classes#
For exposed classes that derive from some classes from the SDK, SAMSON provides convenience factory functions to create instances.
Assume for example a SAMSON Extension provides a particle interaction model:
class InteractionModel : public class SBMInteractionModelParticleSystem {
InteractionModel(SBMDyamicalModelParticleSystem* dynamicalModel);
~InteractionModel();
...
virtual void updateInteractions();
...
};
Then, an instance of this class can be created as follows:
SBMInteractionModelParticleSystem* interactionModel =
SAMSON::makeInteractionModel(dynamicalModel, "InteractionModel");
where dynamicalModel
is the argument that would be passed to the constructor of the interaction model.
If only the reimplemented virtual functions of the interaction model should be used (e.g. updateInteractions), then the interactionModel
pointer is sufficient, even though interactionModel
is a pointer to a SBMInteractionModelParticleSystem
and not a pointer to an InteractionModel
.
General case#
In case the exposed class does not derive from a type for which SAMSON offers convenience functions, such as the makeInteractionModel
function mentioned in the previous section, a class instance may be directly created from a class proxy.
Assume for example a SAMSON Extension contains the following class:
class A {
SB_CLASS
public:
A() {}
virtual ~A() {}
};
SB_REGISTER_TYPE(A, "A", "BF99103E-06FE-C4C1-D929-4C6E833B101C");
as well as the following class descriptor:
SB_CLASS_BEGIN(A);
SB_CLASS_TYPE(SBCClass::App);
SB_CLASS_DESCRIPTION("Empty class");
SB_FACTORY_BEGIN;
SB_CONSTRUCTOR_0(A);
SB_FACTORY_END;
SB_INTERFACE_BEGIN;
SB_INTERFACE_END;
SB_CLASS_END(A);
Then SAMSON may be used to retrieve a proxy to the class A
:
which may then be used to create an instance of class A
:
It must be noted that, in this case, the pointer to the object of class A
return by the proxy is held in a value holder.
Assume a SAMSON Extension called SEElement
defines a class A
, and another SAMSON Extension called SEOtherElement
wants to use it. If the SAMSON Extension called SEOtherElement
does not have access to the declaration of class A
(typically provided in a header file included in SEElement
), it cannot use the symbol A
in its source code. For example, if the source code of SEOtherElement
contained the line:
the compiler would complain that the symbol A
is not defined.
In order to provide access to exposed classes, SAMSON uses a mechanism to hold values (for example, pointers to objects) in a uniform way. In the example above, the constructor exposed in the SAMSON Extension SEElement
returns a value holder that stores a pointer to the new instance of class A
:
and the SAMSON Extension SEOtherElement
sees this value holder as a pointer to a SBValue (the base class of SBValueHolder<A*>
):
Calling functions#
Assume a SAMSON Extension contains the following class:
class A {
SB_CLASS
public:
A() {}
virtual ~A() {}
int multiplyByTwo(int i) {
return 2 * i;
}
};
SB_REGISTER_TYPE(A, "A", "BF99103E-06FE-C4C1-D929-4C6E833B101C");
as well as the following class descriptor:
SB_CLASS_BEGIN(A);
SB_CLASS_TYPE(SBCClass::App);
SB_CLASS_DESCRIPTION("Integer multiplier");
SB_FACTORY_BEGIN;
SB_CONSTRUCTOR_0(A);
SB_FACTORY_END;
SB_INTERFACE_BEGIN;
SB_FUNCTION_1(int, A, multiplyByTwo, int);
SB_INTERFACE_END;
SB_CLASS_END(A);
In this example, the function multiplyByTwo
may be called by other SAMSON Extensions. Since the function is not static, we must first construct an instance of class A
:
We may then call the function:
SBValue* argumentHolder = new SBValueHolder<int>(17);
SBValue* resultHolder = classProxy->call(objectHolder, "multiplyByTwo", argument);
int result = static_cast<SBValueHolder<int>*>(resultHolder)->getValue();
delete argumentHolder;
delete resultHolder;
Here, also, we use value holders in order to wrap arguments provided to the called function. In this example, we are able to unwrap the int held in the value holder with:
because int
is a standard C++ type that all SAMSON Extensions may use. In case the return value of the exposed function was not of a type known by the SAMSON Extension that calls the function, unwrapping would not be possible, but the functionality of the return type may still be accessible through its proxy, and the returned value holder may still be passed as argument to other exposed functions.
Remark: if an exposed function is static, no class instance is required, and the pointer passed to the call
function can be set to 0.
Optimizations#
When multiple instances of the same class are created, it is more efficient to store a pointer to a class constructor:
SBProxy* classProxy = SAMSON::getProxy("A");
SBFactory* classFactory = classProxy->getFactory();
SBConstructor* classConstructor = classFactory->getConstructor();
which we may then use to construct an instance of class A:
Similarly, when multiple calls to the same function are performed, it is more efficient to store a pointer to a class function and reuse it:
SBProxy* classProxy = SAMSON::getProxy("A");
SBInterface* classInterface = classProxy->getInterface();
SBFunction* classFunction = classInterface->getFunction("multiplyByTwo", "int");
We may then use the exposed function by wrapping arguments and unwrapping results: