2.7 Metadata

Most Sh types support storing metadata with their instances. This is data that is not necessary for the object itself to function, but provides potentially useful information relating to the object that might be used by other parts of the system or by the user. Sh automatically generates some metadata (such as the type), but other kinds of metadata are specified by the user.

There are two major reasons to have metadata in Sh. The first is documentation. By providing information such as a name, a title, and a description of a tuple, a user interface containing widgets to manipulate uniform parameters and attributes can be constructed automatically, and can operate on arbitrary programs by querying the metadata. This sort of information can also be useful for debugging purposes.

The second major reason to have metadata is to provide information to the backends. Because the Sh frontend is designed to be as separate from its backends as possible, we do not wish to expose interfaces that rely on particular backend properties at the API level. To provide a clean separation, we allow backends to define and access arbitrary string-based metadata that can be attached to objects with special-purpose meanings. Backend can then be updated or changed without having to change the Sh API and hence without breaking backwards compatibility. This comes at the cost of having to establish conventions for naming this metadata. We expect backends to document the metadata they can make use of appropriately, and to use common conventions whenever possible.

/ E  All types containing metadata inherit from the ShMeta class. This class provides the following member functions:

std::string name() const:
Obtain the name of an object. This may return an internal name if the name has not been set. Normally this should be set to the name of the object in the C++ program, but unfortunately this name cannot be obtained automatically by Sh (see below). This name should not contain spaces and should follow the C++ conventions for identifiers. Longer human readable names can be specified using title.
void name(std::string):
Set the name of an object.
bool has_name() const:
/  E   Returns true if and only if the object’s name has been set explicitly.
bool internal() const:
Determine whether an object is to be considered internal (for example, should not be exposed in a user interface).
void internal(bool):
Set whether an object is considered internal.
std::string title() const:
/ E   Obtain the human-readable name of an object.
void title(std::string):
/  E   Set the human-readable name of an object. This name can be longer than the C++ name and can include spaces.
std::string description() const:
/  E   Obtain a longer description of an object.
void description(std::string):
/  E   Set a longer description of an object. This description should be roughly a paragraph in length and might be used in a tooltip, for instance.
std::string meta(std::string key) const:
/  E   Obtain arbitrary metadata associated with key. Returns an empty string if no data has been assigned to that key.
void meta(std::string key, std::string value):
/  E   Set the arbitrary metadata associated with key to value.

Note that for “thin wrapper” classes, such as ShVariable and ShProgram, the above member functions are defined to forward any queries to the actual reference-counted node object to which they refer. / E   This is done by inheriting from the ShMetaForwarder class.

2.7.1 Object Names

Variables in C++ are usually named. It would be nice if Sh could return these variable names by default when name() is called. Unfortunately C++ has no standard way to access these names at run-time.

It would be theoretically possible to open up a program’s executable file at runtime and search through the symbol table to find the name corresponding to a particular variable address. However, such code would be extremely unportable, and there are many cases where this would not work (e.g. if debugging information were not present in the executable, and possibly in the case of shared libraries).

Another alternative would be to preprocess the source somehow and add in code to mark variable declarations with appropriate calls to name(). This is non-trivial though and adds another level of indirection to the compilation process. We specifically try to avoid having to add external dependencies when using Sh, as one of its prime advantages is that it is merely a C++ library that can easily be added to a project.

Therefore, for portability reasons, we currently recommend that the name() property be set explicitly. Names are used for several introspection purposes besides documentation, and these names unfortunately cannot be set automatically in a reliable fashion.1 Sh provides several macros that make assigning names to objects more convenient. These macros are:


#define SH_NAME(x)        x.name( # x )
#define SH_DECL(x)        x; x.name( # x ); x
#define SH_NAMEDECL(x, n) x; x.name(n); x

Each of these take some variable and name it using the name as it appears in the source program. SH_NAME is intended to be used at some point after declaring a variable. It simply sets the given variable’s name to its C++ name. Writing SH_NAME(foo) is somewhat nicer than having to write foo.name("foo"), and avoids mistakes like foo.name("bar").

The SH_DECL and SH_NAMEDECL macros allow a variable to be named at the same time as it is declared in a function (but not, unfortunately, in a class). The SH_NAMEDECL allows the metadata name to differ from the actual C++ variable name.

Here’s an example of the different ways in which variables can be named:


// Example of SH_NAME
ShAttrib3f unnamed;        // Unnamed. name() returns something like u1234.
ShAttrib3f x; x.name("x"); // Call name() directly.
ShAttrib3f y; SH_NAME(y);  // A little more convenient

// Examples of SH_DECL and SH_NAMEDECL.
// Note how these still allow initializing the object with a value.
// Consider their definitions to see why this works.
ShAttrib3f SH_DECL(z) = ShAttrib3f(1.0, 0.0, 1.0);
ShAttrib3f SH_NAMEDECL(lv, "lightvec");

We recommend that you get into the habit of using SH_DECL wherever possible. This will save you from having to later go back and add the appropriate calls.

One limitation of these macros is that they cannot be used for class member declarations. You have to set the name in the constructor, since methods cannot be called at the same time as declarations in classes.


Note: This manual is available as a bound book from AK Peters, including better formatting, in-depth examples, and about 200 pages not available on-line.