NAP
Resource Management

Overview

Resources are small stand-alone building blocks that you add to your application as content. They are authored in Napkin and stored in .json data files. Resources are used to load an image from disk, define a three-dimensional shape, create a render window, etc. Here is an example of a data file that exposes two resources:

{
"Objects" :
[
{
"Type" : "nap::ImageFromFile",
"mID" : "BackgroundImage",
"ImagePath" : "background.jpg"
},
{
"Type" : "nap::PlaneMesh",
"mID": "PlaneMesh"
}
]
}

The resources mentioned above are both of a different kind and have their own set of properties. All resources in the data file have a C++ counterpart: nap::ImageFromFile and nap::PlaneMesh both exist as classes in the NAP C++ codebase. Both objects are created and initialized when this file is loaded. After creation the C++ properties will match the properties defined in JSON.

Creating Resources

To make your C++ classes editable in Napkin you need to explicitly expose them using the NAP RTTI system. NAP uses a number of macros to ease the way you can expose classes and their properties. To create a resource that can be edited in Napkin you need to derive if from Resource and tell the system what the parent object is. To accomplish this you add to the class declaration the RTTI_ENABLE macro:

class NAPAPI Shader : public Resource
{
RTTI_ENABLE(Resource)
};

And in the .cpp file, the following macros are added:

RTTI_BEGIN_CLASS(nap::Shader)
RTTI_END_CLASS

This is the basis for setting up an RTTI-enabled class. In the example above we defined a shader. The RTTI_ENABLE macro makes sure that the system knows this class is derived from a Resource. The input for the RTTI_ENABLE macro is always the parent object. The code in the .cpp file exposes this class as a resource to NAP. The shader is now available as an object that can be authored in Napkin.

The NAPAPI macro makes sure that the class can be read and accessed (from the outside world) by the NAP system. The compiler (on Windows) won't expose the class to the outside world when you forget to put in the NAPAPI macro. Remember that on startup a NAP application (automatically) loads all modules. NAP tries to find all the exposed resources that are compatible with the NAP system when loading a module. When it encounters a resource in a data file that is not available to the system the resource manager will raise a warning and stop execution. It is therefore important to always expose a class to the outside world using the NAPAPI macro.

Exposing Properties

Properties belong to a resource. You can think of properties as attributes and add as many as you want. To extend a resource with properties you add a property field to your class. In the example below we add two properties: 'VertPath' and 'FragPath':

class NAPAPI Shader : public Resource
{
RTTI_ENABLE(Resource)
public:
std::string mVertPath; // Property: Path to the vertex shader on disk
std::string mFragPath; // Property: Path to the fragment shader on disk
};

And extend the RTTI macro in the .cpp file with information about our properties:

// Adding serialzable properties
RTTI_BEGIN_CLASS(nap::Shader)
RTTI_PROPERTY("VertPath", &nap::Shader::mFragPath, nap::rtti::EPropertyMetaData::FileLink | nap::rtti::EPropertyMetaData::Required)
RTTI_PROPERTY("FragPath", &nap::Shader::mVertPath, nap::rtti::EPropertyMetaData::FileLink | nap::rtti::EPropertyMetaData::Required)
RTTI_END_CLASS

These properties are important. Both properties point to a file on disk that contain GLSL code that is compiled when the shader is initialized. Both properties wrap a class member: 'mFragPath' and 'mVertPath'. These are exposed to the system using the RTTI_PROPERTY macro. The EPropertyMetaData enum provides additional information about properties. In this example the vertex and fragment shader paths are required. This means they must be set. If not the resource manager won't be able to load the shader. NAP exposes the following types of properties:

  • Default : will use the class default if the property was not set
  • Required : loading the data file will fail if the property is not set
  • FileLink : the property defines a relationship with an external file (.bmp, .wav etc.)
  • Embedded : the property is an embedded pointer

The shader can now be created and edited in Napkin. The result in JSON looks something like this:

{
"Type" : "nap::Shader",
"mID" : "FogShader",
"VertPath" : "shaders/fogshader.vert"
"FragPath" : "shaders/fogshader.frag"
}

The resource identifier (mID) can be anything you like. The identifier is used to retrieve a resource in your application and to refer to this object by other objects in Napkin & JSON. More things are possible with the RTTI system. For instance: it has support for constructors with one or more arguments, and it can also expose C++ enums in a way that is still readable. See typeinfo.h or the reference documentation for more detailed information on how to do this.

Pointing to Resources

It is often useful if a resource can access information from another resource. NAP allows you to create links between objects to accomplish just that. A resource can point to other resources by referring to the identifier of a resource. In C++, we use a specific type of pointer to accomplish this: a resource pointer. Let's assume there is a resource called 'Material' that points to a 'Shader'. The material wants to use the information stored in the shader and exposes that in the form of a link to a shader. The material can now access the shader without having to worry about order of initialization. When the material is initialized the shader has been created and validated. You only have to implement the logic you want to perform based on the information that is present in the shader:

class NAPAPI Material : public Resource
{
RTTI_ENABLE(Resource)
public:
ResourcePtr<nap::Shader> mShader; // Property: Link to a 'Shader' resource
};

In the header the material exposes (as a member) a link to a shader in the form of an object pointer:

RTTI_BEGIN_CLASS(nap::Material)
RTTI_END_CLASS

In the .cpp file we register the material as a resource and add the link to a shader as a property of the material. This is similar to how we just defined the shader and its properties. We can now create and link the items together in Napkin, with as a result:

{
"Objects" :
[
{
"Type" : "nap::Shader",
"mID" : "FogShader",
"VertPath" : "shaders/fogshader.vert"
"FragPath" : "shaders/fogshader.frag"
}
{
"Type" : "nap::Material",
"mID" : "FogMaterial",
"Shader" : "FogShader"
}
}
}

Note that the material points to the shader using the mID: 'FogShader'.

The material is now registered and exposes (as property) the link to a shader. After calling loadfile(), these two objects will be created and the pointers will be ‘resolved’. This means that they will point to the right resource. In this case the material points to a 'FogShader'. The resource manager also makes sure that the shader is initialized before the material. Cyclic dependencies are unfortunately not (yet) supported. It is not possible to point to a resource that eventually points back at the original resource. The system cannot determine the correct order of initialization in these situations.

Real Time Editing

NAP contains a powerful ‘real-time editing’ engine. This means that changes you make (in the editor or to external content) are picked up by the running application in real-time This results in extremely fast iteration times. NAP watches the data file that is loaded, including any external files that are referenced (such as textures or audio files). When changes to these files are detected they are instantly hot-loaded into the system. All it takes from a user perspective is to press 'save'.

From a programmer perspective there are some rules to adhere to. Each resource follows the same initialization pattern. When a file is loaded, all objects are initialized in the correct order. When an init() call returns false it means that resource could not be initialized correctly. In that case, the entire load is cancelled and the system returns to the original state before load was called. The returned error message describes what went wrong during (re)loading. It is guaranteed that all the objects remain in their original state until the initialization sequence is completed successfully. When there is no original state (ie, the file is loaded for the first time) and initialization fails the application will exit immediately.

Here are the rules for writing a correct init function:

  • Return true on success, false on failure
  • Only assert (or halt program execution in any other way) on programmer errors, never on user errors
  • If the function fails make sure that a clear error message is presented to the ErrorState object that is passed to init().
  • Make sure that the init() function does not have any side effects. This means that it should not update any global state. The system cannot revert such changes in case of an error.

ErrorState is a class that offers a convenient way to report an error to a user. The general pattern is as follows:

if (!errorState.check(loadImage(), "Failed to load image %s, dimensions are unsupported (%d:%d)", mPath.c_str(), mDimensions.x, mDimensions.y))
return false;

The pattern is somewhat similar to the way asserts work: the first parameter is evaluated. When it evaluates to false the error message is stored in the ErrorState object. Notice that multiple messages can be stacked in the error object. This is convenient in many situations where a very low-level message is generated, but the context where the error occurred is missing. By nesting check() calls in various functions the context can still be provided.

Linking Media

In the real time editing section we briefly touched upon linking to external files. Some objects read information from other files. Examples include a texture resource that reads (among others) .png files or an audio player that reads .wav or .mp3 files. The real-time editing system will reload any of these external files when a modification to them is made. For this to work you need to tell the system if a property is a link to a file. You do this by marking a property of a resource as a file link:

RTTI_BEGIN_CLASS(Texture)
RTTI_PROPERTY("Path", &Texture::mPath, nap::rtti::EPropertyMetaData::FileLink)
RTTI_END_CLASS

Working With Arrays

You can group items together into an array. This works for all primitive types, simple structs (such as a color) and links to objects, including resources and components. The video modulation demo uses two arrays: one to create a group of videos and another to create a group of meshes. In the application the user can select a video or mesh based on an index.

In C++ an array is a regular vector. This vector becomes an array property of the resource, similar to how you normally expose a property. In the example below we add a member called 'mVideoFiles'. This is an array of video files that the user can choose from. Every video in the array is a link to an existing video resource:

class NAPAPI VideoContainer : public rtti::RTTIObject
{
RTTI_ENABLE(rtti::RTTIObject)
public:
std::vector<ResourcePtr<Video>> mVideoFiles;
}

and in the .cpp file we register the vector as a regular property:

// nap::VideoContainer
RTTI_BEGIN_CLASS(nap::VideoContainer)
RTTI_PROPERTY("Videos", &nap::VideoContainer::mVideoFiles, nap::rtti::EPropertyMetaData::Required)
RTTI_END_CLASS

The system recognizes that the property is an array of video links. You can now author the array in Napkin, with as a (possible) result:

{
"Type": "nap::Video",
"mID": "SnowVideo",
"Path": "snow.mp4"
},
{
"Type": "nap::Video",
"mID": "StreakVideo",
"Path": "streak.mp4"
},
{
"Type": "nap::VideoContainer",
"mID": "VideoContainer",
"Videos":
[
"StreakVideo",
"SnowVideo"
]
}

The example above is a simplification of the classes used in the video modulation demo.

Structs, Classes and Resources

Up to this point we only worked with resources. Resources are classes. But sometimes you want to use a simple struct to define (for example) a color or date. NAP supports structs in data, but they have to be declared in a different way. Let's create a simple RGB color in C++:

// RGB Color
class NAPAPI Color
{
public:
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
};

As you can see the color isn't a native C++ struct but a class. This is because the RTTI system does not support C++ structs. What we do instead is label it as a struct in the .cpp file:

RTTI_BEGIN_STRUCT(Color)
RTTI_PROPERTY("r", &Color::r, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("g", &Color::g, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("b", &Color::b, nap::rtti::EPropertyMetaData::Default)
RTTI_END_STRUCT

So what are the advantages of using a struct? A struct is a lightweight object that can be copied by value. When the system encounters a struct it is created using copy construction instead of being new'd. This is faster when the object is small and allows the editor to add and remove simple structures from an array. So in short, when you have a simple object that is:

  • Used to group some data under a common name
  • Easy to copy
  • Not a resource
  • Required to be editable in Napkin / JSON ..

.. define it as a struct.

In all other cases: define it as a class. But what is the difference between a class and resource? A resource is a stand-alone building block that can be declared independent of others, as a top level object, in data. It must carry an identifier and is initialized and resolved by NAP after construction. Every typeof resource must be derived from Resource.

On the other hand, classes and structs that are not a resource can't be declared independent of other objects. They must and a parent and won't be initialized or resolved.

Classes (and structs) that are not a Resource can't be declared independent of other objects. They must have a parent and won't be initialized or resolved. You can only use these objects as an embedded object. Only when a class is derived from a Resource is it considered to be a resource to the system.

This might sound confusing but try to follow these rules: Do I need to author my class or struct in Napkin?

  • Yes
    • Is it a resource?
      • Yes
        • Derive your class from Resource (.h)
        • Or any object that is derived from Resource (.h)
        • Always implement the RTTI_ENABLE macro (.h)
        • Define it as a class (.cpp)
      • No
        • Is it a simple structure?
          • Yes
            • Don't implement the RTTI_ENABLE macro (.h)
            • Define it as a struct (.cpp)
          • No
            • Implement the RTTI_ENABLE macro (.h)
            • Leave the RTTI_ENABLE macro empty if there is no parent class (.h)
            • Define it as a class (.cpp)
  • No
    • Don't expose it to the system
    • Declare it as a regular C++ class

This diagram doesn't take components into account. You can read more about components in a later section.

Devices

A device is a special type of resource. You can think of a device as a class that represents and manages the connection to an external piece of hardware (such as a DAC) or a computer. Every device has a start() and stop() method that you can override. Both methods are called by the resource manager at the appropiate time, ie: when the device is created, has changed or is removed from the resource tree. The resource manager 'stops' a device that is running before it is destroyed.

NAP ships with a couple of devices such as the OSCReceiver, OSCSender, ArtnetController, EtherDreamDac etc.

Embedding Objects

C++ objects are often embedded into each other. For example:

// RGB Color
class NAPAPI Color
{
...
};
// Palette that contains two colors
class NAPAPI Palette : public Resource
{
RTTI_ENABLE(Resource)
public:
Color mColorOne; // First Color of the palette
Color mColorTwo; // Second Color of the palette
};

Here we see a 'Palette' that contains two colors. Wouldn't it be nice to assign both colors to the palette directly? As we saw before, a 'Color' is a struct, not a resource. Therefore, we can't create a single independent color as a resource in Napkin. But NAP can create and assign registered structs (and classes) on the fly when it encounters them in the file. We call these objects 'embedded objects', or 'compounds'. For this to work it's important that both objects have their properties registered in the .cpp file:

RTTI_BEGIN_STRUCT(Color)
RTTI_PROPERTY("r", &Color::r, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("g", &Color::g, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("b", &Color::b, nap::rtti::EPropertyMetaData::Default)
RTTI_END_STRUCT
RTTI_BEGIN_CLASS(Palette)
RTTI_PROPERTY("ColorOne", &Palette::mColorOne, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("ColorTwo", &Palette::mColorTwo, nap::rtti::EPropertyMetaData::Default)
RTTI_END_STRUCT

Possible result in JSON:

{
"Objects" :
[
{
"Type" : "Palette",
"mID" : "MyPalette",
"ColorOne" :
{
"r" : 0.0,
"g" : 0.5,
"b" : 0.7
},
"ColorTwo" :
{
"r" : 1.0,
"g" : 0.25,
"b" : 0.5
}
}
}
}

When creating the palette the system finds the two colors and assigns them on the spot. Notice that Color isn't derived from Resource. It doesn't need to because it's not intended to be a resource. The system does however need to know the properties of a color: in this case the three color channels. You can however embed any object, including objects derived from RTTIObject. It's perfectly valid to embed an image into another resource directly.

Embedding Pointers

Often pointers are used to link to other resources. For example:

// A palette container links to a color palette
class NAPAPI PaletteContainer : public Resource
{
RTTI_ENABLE(Resource)
public:
ResourcePtr<Palette> mColorPalette; // Property: Link to a color palette
};

In this example, PaletteContainer points to a color palette. Both the container and the color palette are root objects in the data file, which is sometimes messy and harder to read. For this reason, NAP supports ‘embedded pointers’. If an object logically belongs to another object, you can mark the pointer ‘embedded’, and the syntax will become similar to the way that compound objects are written. The RTTI definition in the .cpp needs to change slightly:

RTTI_BEGIN_CLASS(Foo)
RTTI_PROPERTY("ColorPalette", &PaletteContainer::mColorPalette, nap::rtti::EPropertyMetaData::Embedded)
RTTI_END_CLASS

With the declaration in JSON looking something like this:

{
"Objects" :
[
{
"Type" : "PaletteContainer",
"mID" : "MyPaletteContainer",
"ColorPalette" :
{
"ColorOne" :
{
"r" : 0.0,
"g" : 0.5,
"b" : 0.7
},
"ColorTwo" :
{
"r" : 1.0,
"g" : 0.25,
"b" : 0.5
}
}
}
}
}

Note that the embedded object is still a regular object that lives inside the resource manager. The identifier can be omitted, in that case an identifier will be generated for you. If you declare an identifier you can still find it by that name in the resource manager.

nap::rtti::EPropertyMetaData::Embedded
@ Embedded
An embedded pointer.
nap::rtti::EPropertyMetaData::FileLink
@ FileLink
Defines a relationship with an external file.
nap::rtti::EPropertyMetaData::Required
@ Required
Load will fail if the property isn't set.
nap::Material::mShader
ResourcePtr< Shader > mShader
Property: 'Shader' The shader that this material is using.
Definition: material.h:163
nap::Material
Definition: material.h:83
nap::Shader
Definition: shader.h:105
nap::rtti::EPropertyMetaData::Default
@ Default
Uses the (class) default if the property isn't set.