Iray SDK API nvidia_logo_transpbg.gif Up
Database Access

Provides access to the database using concepts like scopes and transactions. More...

Classes

class  mi::neuraylib::IDatabase
 This interface is used to interact with the distributed database. More...
 
class  mi::neuraylib::IScope
 A scope is the context which determines the visibility of database elements. More...
 
class  mi::neuraylib::ITransaction
 A transaction provides a consistent view on the database. More...
 

Detailed Description

Provides access to the database using concepts like scopes and transactions.

Database limitations

The database does not support certain usage patterns. These patterns can not be rejected programmatically, but need to be ensured by the user for proper operation.

Note
This section mentions some DB internals, e.g. tags, which are not further explained here.

Re-use of names of DB elements eligible for garbage

collection

The database requires that database elements that are eligible for removal by the asynchronous garbage collection must not be accessed under any circumstances. Failure to observe this limitation can have fatal consequences, i.e., lead to crashes.

When is a DB element eligible for garbage collection? For simplicity, we assume that there is only a single scope, and no parallel transactions. A DB element becomes eligible for garbage collection if (a) the DB element has been flagged for removal via mi::neuraylib::ITransaction::remove() in this or a past transaction, and if (b) at the end of a transaction the DB element is no longer referenced by other DB elements. From this point on, the asynchronous garbage collection may remove the element at any point in time and the element must not be accessed anymore under any circumstances.

Example (invalid re-use of names):

neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> scope( database->get_global_scope());
scope->create_transaction());
{
transaction1->create<mi::neuraylib::IGroup>( "Group"));
// For simplicity, creation and flagging for removal in the very same transaction.
transaction1->store( group.get(), "foo");
transaction1->remove( "foo");
}
// The DB element "foo" is eligible for garbage collection after this call.
transaction1->commit();
scope->create_transaction());
// Invalid re-use of the name "foo".
transaction2->access<mi::neuraylib::IGroup>( "foo"));
transaction2->commit();
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
This interface is used to interact with the distributed database.
Definition: idatabase.h:294
A group is a container for other scene elements.
Definition: igroup.h:39

Complying with this limitation can be quite difficult since names might not be under the user's control, e.g. due to the name occurring in a given scene, or if names are determined by Iray itself as for many MDL elements.

Strategy A: Ensure that the DB element has actually been removed before its name is re-used. This can be achieved by an enforced, synchronous run of the garbage collection between the end of the transaction in which the DB becomes eligible for garbage collection and the next use of that name (see mi::neuraylib::IDatabase::garbage_collection()). Keep in mind though that other, parallel transactions might delay the point in time at which a DB element becomes eligible for garbage collection. While this approach is usually rather simple to implement, it causes a delay due to ending the current and starting a new transaction, and the synchronous garbage collection run itself. In addition it might not have to intended effect due to parallel transactions.

Strategy B: Avoid that the DB element becomes eligible for garbage collection. An obvious approach not to flag DB elements for removal at all is usually not an option. But it is possible to delay the eligibility for garbage collection to a later point in time when strategy A can be easily implemented: Create an attribute container with a user-defined attribute of type "Ref[]" which references all DB elements flagged for removal. At a favorable time for synchronous garbage collection, flag this attribute container for removal, and use strategy A. Care needs to be taken that the attribute container is not edited from parallel transactions (see mi::neuraylib::ITransaction for the semantics of edit operations in parallel transactions). Note that this strategy just delays the problem, but does not solve it by itself.

Most often these invalid re-uses of names occur when one scene is unloaded in order to load another scene. These situations are especially susceptible to timings that cause the fatal consequences like crashes. While not a full strategy in itself, there is simple way to avoid the invalid re-use in these situations:

Strategy C: Use a different scope for each scene. The recommendation is not to use the global scope at all, but different child scopes for each scene. The different scope means that names from other scopes are not visible for the current scope, and, therefore, do not count as re-use of that name. Besides, using separate scopes has also the benefit that one can easily get rid of a particular scene without the risk of leaving some remnants behind due to missing removal flags.

Identical names due to different scopes

See mi::neuraylib::IScope for general documentation about scopes.

Be careful when storing DB elements of the same name in different scopes. If the scopes are not in a parent-child relation, then the following limitation does not apply since no transaction will ever see both elements. But if the scopes are in a parent-child relation, then it is required that the store operation happens first in the parent scope and that this element is visible in the child scope before the store operation in that child scope occurs (in the same transaction, or with different transactions where the first one is committed before the second one is started). Otherwise, this results in different DB elements of the same name (and not just different versions of the same DB elements as it would happen when the correct order is observed).

Example (creation in wrong order):

neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> parent_scope( database->get_global_scope());
database->create_scope( parent_scope.get()));
// DB element with name "foo" is created first in the child scope ...
child_scope->create_transaction());
{
child_transaction->create<mi::neuraylib::IGroup>( "Group"));
child_transaction->store( group.get(), "foo");
}
child_transaction->commit();
parent_scope->create_transaction());
{
parent_transaction->create<mi::neuraylib::IGroup>( "Group"));
// ... and is not visible here.
parent_transaction->store( group.get(), "foo");
}
parent_transaction->commit();
// The admin HTTP server shows now that the name "foo" maps to two different tags. Both tags
// are visible from the child scope via tag references (not part of this example), but only
// one of them via name.

While such a situation is not necessarily a problem for the database itself, it leads to unexpected behavior on the user side, as accesses might return different instances, depending on the details of the access method.

This limitation does not apply if there is already a DB element with name accessible at the time of the store operation, i.e., the method does not create a new DB element, but essentially overwrites/edits an existing one.

Identical names due to parallel transactions

See mi::neuraylib::ITransaction for general documentation about transactions.

The database does not support the storing of DB elements in parallel transactions, unless these DB elements have different names or the scopes are different and are not in a parent-child relation to each other. Failure to observe this limitation results in different DB elements of the same name (and not just different versions of the same DB elements as it would happen with serialized transactions).

Example (wrong creation in parallel transactions):

neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> scope( database->get_global_scope());
scope->create_transaction());
scope->create_transaction());
{
transaction1->create<mi::neuraylib::IGroup>( "Group"));
transaction1->store( group.get(), "foo");
}
{
transaction2->create<mi::neuraylib::IGroup>( "Group"));
transaction2->store( group.get(), "foo");
}
transaction1->commit();
transaction2->commit();
// The admin HTTP server shows now that the name "foo" maps to two different tags. Both tags are
// visible via tag references from the global scope (not part of this example), but only one of
// them via name.

While such a situation is not necessarily a problem for the database itself, it leads to unexpected behavior on the user side, as accesses might return different instances, depending on the details of the access method.

This limitation does not apply if there is already a DB element with name accessible at the time of the store operations, i.e., the method does not create new DB elements, but essentially overwrites/edits an existing one.

Note that editing (as opposed to storing) the very same DB element in parallel transactions is supported by the database, but it is discouraged, since the semantics might not be as desired (see mi::neuraylib::ITransaction).

References to elements in more private scopes

See mi::neuraylib::IScope for general documentation about scopes.

Be careful when creating references to DB elements that exist in a different scope than the referencing element. References to elements in parent scopes (or the the same scope) are perfectly fine. But you must not create references to DB elements that exist only in a more private scope. Typically, this happens when using mi::neuraylib::ITransaction::store() with an (explicit) wrong privacy level.

Example (invalid reference to element in more private scope):

neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> parent_scope( database->get_global_scope());
database->create_scope( parent_scope.get()));
child_scope->create_transaction());
{
child_transaction->create<mi::neuraylib::IGroup>( "Group"));
check_success( 0 == child_transaction->store( bar.get(), "bar"));
child_transaction->create<mi::neuraylib::IGroup>( "Group"));
check_success( 0 == foo->attach( "bar"));
// Triggers an error message since "foo" is to be stored in the parent scope (due to explicit
// privacy level 0), but references "bar" in the child scope.
check_success( 0 == child_transaction->store( foo.get(), "foo", 0));
}
child_transaction->commit();
parent_scope->create_transaction());
{
parent_transaction->access<mi::neuraylib::IGroup>( "foo"));
// Triggers a fatal message about an invalid tag access.
const char name = foo->get_element( 0);
}
parent_transaction->commit();

A reference to an element in a more private scope triggers an error message when the referencing element is stored in the DB, but does not prevent the operation from being completed nor is it signaled via a return code. As soon as the incorrect reference is used, this triggers a fatal error message (and the process aborts if it returns from the logger callback). Even if the incorrect reference is never used, its existence hints at a conceptual error in the way the application uses scopes.