-
Notifications
You must be signed in to change notification settings - Fork 6
Manual: Exposing Variables for Save Load and Editor
In order for a variable to be visible by the editor, and for it to be saved to and/or loaded from disc, then a class must inherit from ObjectIO
. By default, numerous core classes already inherit from ObjectIO
, including:
SceneObject
ARenderable
ARoutine
Transform
Once your class derives from ObjectIO
, numerous methods and members are inherited. Of primary interest are the onSave
and onLoad
methods, as well as the OCULAR_EXPOSE
macro.
To expose any trivial variables, one must simply call OCULAR_EXPOSE(x)
where x
is the variable you wish exposed. This call must be made before any arbitrary onLoad
or onSave
invocations, so ideally it should be placed in the constructor(s). If the variable is trivial, then nothing else needs to be done as the ObjectIO
implementation of the save and load methods will handle these automatically (see is_trivial
for what defines a trivial type).
If the variable is not trivial, then it must also inherit from ObjectIO
or it must be directly transformable to and from a string (see Ocular::Utils::String). Aside from that, once again it will be automatically saved and loaded.
Example:
class MyRoutine : public ARoutine
{
public:
MyRoutine();
virtual ~MyRoutine();
float someFloat;
protected:
Matrix4x4 m_SomeMatrix;
private:
Transform m_SomeTransform;
};
MyRoutine::MyRoutine()
: ARoutine()
{
OCULAR_EXPOSE(someFloat); // float is a trivial type
OCULAR_EXPOSE(m_SomeMatrix); // Matrix4x4 can be converted directly to a string
OCULAR_EXPOSE(m_SomeTransform); // Transform inherits from ObjectIO
}
In general, if there is a valid Ocular::Utils::TypeName<T>::name
for the type or it inherits from ObjectIO
, then it may be exposed. In order to expose custom types, see the Exposing Custom Types section.
If a pointer can be guaranteed to be instantiated prior to any load/save calls or exposable inspections, and it can also be guaranteed to be valid and exist for the lifetime of the object, then one can simply follow the directions in the Exposing Variables section.
Otherwise in order to save and load it to disc, one must override onLoad
/onSave
and handle it manually.
The onLoad
/onSave
methods interact with what is called a Builder Node Chain. Each instance of a BuilderNode
has the following properties:
- Name
- Type
- Value
- Parent
- Children
Name/Type/Value are all strings, which allows the various loaders and savers to interact with the object data in a generic manner. So, during loading one will be pulling needed information out of a BuilderNode
, and during saving one will be adding nodes to a pre-existing chain. One may also use overridden load and save methods as a way to transfer data to disc without directly exposing it for others to inspect or modify.
At this point variables should be handled in one of two ways. The first approach is for any trivial variables that can be directly converted to and from a string.
void Foo::onLoad(BuilderNode const* node)
{
ObjectIO::onLoad(node);
if(node)
{
// We have a Vector3f we did not want to directly expose.
const BuilderNode* vectorChild = node->getChild("m_Vector");
if(vectorChild)
{
m_Vector = OcularString->fromString<Vector3f>(vectorChild->value);
}
// Here is a boolean pointer we could not guarantee
if(m_pBoolean)
{
cconst BuilderNode* booleanChild = node->getChild("m_pBoolean");
if(child)
{
&m_pBoolean = OcularString->fromString<bool>(booleanChild->value);
}
}
}
}
void Foo::onSave(BuilderNode* node)
{
ObjectIO::onSave(node);
if(node)
{
// addChild(name, type, value)
node->addChild("m_Vector", TypeName<Vector3f>::name, OcularString->toString<Vector3f>(m_Vector));
if(m_pBoolean)
{
node->addChild("m_pBoolean", TypeName<bool>::name, OcularString->toString<bool>(&m_pBoolean));
}
}
}
For any types that do not have a direct to/from string conversion, then they must inherit from ObjectIO
. They can be manually saved and loaded as shown below.
void Foo::onLoad(BuilderNode const* node)
{
ObjectIO::onLoad(node);
if(node)
{
// We have a Transform we did not wish to expose
const BuilderNode* transformChild = node->getChild("m_Transform");
if(child)
{
m_Transform.onLoad(transformChild);
}
// We have an ARoutine we could not guarantee
if(m_pRoutine)
{
const BuilderNode* routineChild = node->getChild("m_pRoutine");
if(child)
{
m_pRoutine->onLoad(routineChild);
}
}
}
}
void Foo::onSave(BuilderNode* node)
{
ObjectIO::onSave(node);
if(node)
{
// addChild(name, type, value)
BuilderNode* child = node->addChild("m_Transform", TypeName<Transform>::name, "");
if(child)
{
m_Transform.onSave(child);
}
if(m_pRoutine)
{
child = node->addChild("m_pRoutine", TypeName<ARoutine>::name, "");
if(child)
{
m_pRoutine->onSave(child);
}
}
}
}
It should be noted, that one will always be provided the immediate BuilderNode
for the scope of the object. Any reading or writing in the Builder Node Chain should be done to the node that was directly given. Likewise, if invoking a child's save/load methods, then one should ensure that they are providing the proper child node to operate upon.
While by default OCULAR_EXPOSE
works with essentially all primitives and numerous core Ocular classes, one may need the ability to expose their own custom class. Doing so is easy, and requires only a few steps.
First, the type must be properly registered using the OCULAR_REGISTER_TYPE
macro (or OCULAR_REGISTER_TYPE_CUSTOM
).
namespace CustomNamespace
{
class CustomType
{
// ...
};
}
OCULAR_REGISTER_TYPE(CustomNamespace::CustomType);
or
OCULAR_REGISTER_TYPE_CUSTOM(CustomNamespace::CustomType, "CustomType");
If explicitly specifying the name, one must ensure there are no types already registered with the desired name. If there are, there may be conflicts when performing toString
, fromString
, etc.
The second step varies on how the type should be represented. Either the type should be directly convertible to/from string or it should inherit from ObjectIO
. Existing examples of both:
Direct Conversion To/From String
- Primitives (
uint32_t
,float
,bool
, etc.) - Ocular Math classes (
Vector3f
,Matrix4x4
,Quaternion
, etc.) std::string
- etc.
Inherit ObjectIO
SceneObject
ARoutine
ARenderable
Transform
- etc.
To expose via string conversion, see the manual on String Conversions.
Otherwise, ensure the type correctly exposes any necessary variables and properly overrides it's onLoad
/onSave
as needed.