The Basics of Working With Objects
Creating Objects
C++
To create an object, use Bifrost::createObject()
to create an instance of a Bifrost::Object
known simply as Object
. To then get and set properties on the object use the getProperty()
and setProperty()
methods on the returned object. See the API reference section for a complete listing of methods on the Bifrost::Object
class.
To inspect an object's properties use the Bifrost::Object::keys()
method.
Graph
To create an object, use a value
node with type Object
. To set properties on the object connect the output of the value node to a set_property
node. To get a property value from an object use the get_property
node.
To inspect the properties of an object, use a watchpoint or the dump_object
node.
Value Semantics
The Bifrost graph programming model follows value semantics. All data is treated as immutable and may not be modified. Nodes that modify their input data must conceptually copy the data then modify the copy. The outputs of the node are the copied data.
The Amino compiler will optimize this process and create true copies of data only when necessary. Developers do not need to concern themselves with these optimizations when authoring compound nodes. However they do need to be aware of this mechanism when authoring C++ nodes.
In C++, Amino has the concept of managed types. Managed types are data types that do not directly flow through graphs. Instead, Amino::Ptr
s to the data flow through the graph. Bifrost::Object
is a managed type. When writing C++ nodes that accept objects as inputs, the node's function signature should be written with either an Amino::Ptr<Bifrost::Object>
or a const Bifrost::Object&
as a parameter. Note that in either case, the object is constant and may not be modified. Output parameters should be written as Amino::Ptr<Bifrost::Object>&
. To modify an object, call the toMutable()
method on the Amino::Ptr
argument to the node. This method will return an Amino::Ptr
to a non-constant copy of the object. The object can be modified through this pointer. The code below demonstrates how a C++ operator may modify an object.
inline void modify_object_node(Amino::Ptr<Bifrost::Object> in_object,
Amino::Ptr<Bifrost::Object>& out_object) {
// Does not compile. in_object is const:
// in_object->setProperty("newProperty", "newValue");
// Create a mutable pointer to the object. Note that after this call
// in_object is set to a nullptr and must not be de-referenced.
auto out = in_object.toMutable();
out->setProperty("newProperty", "newValue");
out_object = std::move(out);
}
A Bifrost::Object
can hold managed types stored as Amino::Ptr
. To retrieve an Amino::Ptr
to the property, call Bifrost::Object::getProperty()
. Then, convert it to a non-constant pointer by calling toMutable()
, allowing you to modify the property through this pointer. Finally, use Bifrost::Object::setProperty()
to set the modified value back into the object.
inline void modify_managed_type(Amino::Ptr<Bifrost::Object> in_object,
Amino::Ptr<Bifrost::Object>& out_object) {
// Get an editable object...
auto out = in_object.toMutable();
assert(!out);
// Retrieve an array property named "floatProperty".
// Value semantics implies that the property is const.
// An array is stored in a Bifrost::Object as an Amino::Ptr<>, which is const.
const char* name = "floatProperty";
Amino::Any anyValue = out->getProperty(name);
auto floatArrayConst = Amino::any_cast<Amino::Ptr<Amino::Array<float>>>(std::move(anyValue));
if (floatArrayConst) {
// Create a mutable pointer to the array.
auto floatArray = floatArrayConst.toMutable();
// Modify the array...
floatArray->resize(10);
// Set it back into the editable object.
// Note: do not use the floatArray pointer after this call since it was moved.
out->setProperty(name, std::move(floatArray));
}
// Set the output
out_object = std::move(out);
}
Objects can store other objects as properties. To modify a nested object, repeat the process described above. First acquire a writable pointer to the parent object, then acquire a writable pointer to the nested object, modify the nested object, then set it back into the writable version of the parent object.
inline void modify_subobject_property(Amino::Ptr<Bifrost::Object> in_object,
Amino::Ptr<Bifrost::Object>& out_object) {
const char* name = "objectSubProperty";
const char* subname = "subObjectProperty";
// Get a const pointer to the sub-object.
Amino::Any anyValue = in_object->getProperty(name);
auto subObjectConst =
Amino::any_cast<Amino::Ptr<Bifrost::Object>>(std::move(anyValue));
if (subObjectConst) {
// Get a writable pointer to the sub-object.
auto subObject = subObjectConst.toMutable();
// Edit the sub-object.
subObject->setProperty(subname, "newValue");
// Get a writable pointer to the object.
auto out_object_mut = in_object.toMutable();
// Set the modified sub object into the parent object.
out_object_mut->setProperty(name, std::move(subObject));
out_object = std::move(out_object_mut);
} else {
out_object = in_object;
}
}
Object Factory
As mentioned in Bifrost Object, conceptual types (such as materials, cameras, and geometries) are represented using a property-based sub-typing of Bifrost objects. JSON schema files are used to define these conceptual types.
C++
To create an object conforming to a schema use the Bifrost::ObjectFactory
class. To check if an object conforms to a specific schema use the Bifrost::isA
function.
Graph
To create an object conforming to a schema use the create_object_from_schema
node.