Watchpoint class
A host application that provides Bifrost graph authoring features, like the Bifrost integration in Autodesk Maya, allows a user to create a watchpoint on any incoming graph connection linked to a given node's input port. During graph execution, such a watchpoint allows observing the data that flows through the connection down to the port the watchpoint is attached to. In Bifrost, values flowing along graph connections may have different Bifrost types, so each data type to be observed in the host application will need specific handling.
The Watchpoint class allows exposing to the user the data of one or multiple Bifrost types handled by Watchpoints. Using the Watchpoint class, a developer can create a Watchpoint shared library that will be host-agnostic (i.e. it could work with multiple Bifrost host applications).
Multiplicity vs Watchpoint shared libraries
The Executor SDK allows registering multiple Watchpoint shared libraries for a given host application, and each Watchpoint shared library can support observing the data values for multiple Bifrost types.
But only one Watchpoint shared library can be registered for any given Bifrost type. When registering a new Watchpoint shared library for a host, the Executor SDK will check all data types that it supports (see getSupportedTypeNames() method), and if a Watchpoint shared library was previously registered for a given supported type, a warning message will be issued, and the previously registered library will continue to be used to observe values of this type, ignoring the new library for this type.
Overview of the watchpoint operating process
Here is a brief description of the watchpoint operating process, from their creation to their destruction.
At creation time
- A watchpoint is first created by the host application on an incoming connection of a node's input port in the graph. To do so, the host application must have Bifrost graph authoring capabilities.
- The host application then retrieves the Watchpoint shared library that was registered for the type of data flowing along the connection, and uses the previously created Watchpoint instance from this library (if any), or create a new one otherwise. Note also that when the data type flowing along the connection changes, the Watchpoint instance for connection may be replaced by another instance supporting the new data type.
- Lastly, a new and unique
Records
instance associated to this Watchpoint is created by the host and passed to the Watchpoint'screateClientData()
method, letting the Watchpoint instance create its own custom client data for this watchpoint. TheRecords
instance should be stored in the newly created custom client data so it can be used by the Watchpoint instance during graph execution to record information about the values flowing through this watchpoint.
At execution time
During graph execution, when a new value flows through a connection that is observed by a watchpoint, the Watchpoint's callback function is called, passing to it this Watchpoint's custom client data and the value flowing through the connection. The Watchpoint instance should then record all information to be reported to the user regarding this data value. Such information must be stored in the Records
instance and will be used by the host application once the graph execution is completed.
After graph execution
Once the graph has been executed, the host application can retrieve all information recorded by all Watchpoints and report it back to the user as it sees fit. To do so, the host will call the Watchpoint's getAvailableParameters()
method as well as retrieving the specific parameter values recorded by the Watchpoint's callback function in the Records
instance during graph execution.
At removal time
When the host application removes the watchpoint from a graph connection, the cleaning steps are performed in this order:
- The host application first requests the Watchpoint instance to destroy the client data (if any) by calling the Watchpoint's
releaseClientData()
method. - The host application then deletes the
Records
instance it had passed to the Watchpoint'screateClientData()
method.
At destruction time
When the host application is done with the Watchpoint instance (e.g. possibly when the host application is closed), it will call the Watchpoint's deleteThis()
to delete such instance before unloading the Watchpoint shared library.
getSupportedTypeNames method
A Watchpoint shared library must declare all the Bifrost types it handles. These supported types are declared by overriding the Watchpoint::getSupportedTypeNames()
method and listing their fully qualified names.
createClientData and releaseClientData methods
To be able to record information about the data flowing through a specific watchpoint, a Watchpoint instance must at least create a custom client data when the host application calls its createClientData()
method, and store in it the reference to the Records
instance that it receives.
If the createClientData()
method creates and returns a pointer to a custom client data, then this client data pointer will later be passed to its callback function and to its releaseClientData()
method.
getAvailableParameters method
The getAvailableParameters()
method is called by the host application to retrieve the names of the parameters that you want to expose for a watchpoint when observing a given Bifrost type. For example, if the data type observed by a watchpoint is a float
, you may want to expose the minimum and maximum floating-point values that reach that watchpoint during a graph execution. To do so, you would declare two parameter names for this data type, say "min" and "max", and return these names from the getAvailableParameters()
method as a list of strings.
getParameterDetails method
The getParameterDetails()
method is deprecated and not used anymore.
Callback function and the Records instance
The callback function of a given Watchpoint instance is the function that is called during the graph execution each time a value flows along a graph connection where a watchpoint was created. The Executor SDK obtains from the Watchpoint instance a pointer to this callback function by calling its getCallBackFunction()
method. The signature of this callback function is declared in the Watchpoint class.
The clientData
parameter of the callback function is the client data that was created for this specific Watchpoint, and the valueData
parameter is the value that currently flows along the graph connection at this moment of the graph execution. The locationID
parameter is currently unused and can be ignored.
Using the clientData
, the callback function can retrieve the reference to the Records
instance that it received when its createClientData()
method was called. The Records
instance can be used to store values for the parameters that you want to expose to the user for the data type observed by a watchpoint.
For example, if you want to expose the minimum and maximum values that reach a watchpoint exposing a float
data type during a graph execution, you could declare two parameter names, say "min" and "max", using the getAvailableParameters()
method. Then each time your callback function is called, you would store in your custom client data the actual minimum and maximum values that reached your Watchpoint, and also record those minimum and maximum values that you want to expose to the user by using the Records::set()
method for both the "min" and "max" parameter names. After the graph execution, the host application could then retrieve those "min" and "max" parameter values for this watchpoint and expose them to the user.
Concurrency
A Bifrost graph may contain elements to be evaluated in parallel, like within a for_each
loop. If a watchpoint is created on a graph connection that may be evaluated in parallel, the callback function of such a Watchpoint will be called concurrently by multiple threads. It is therefore important that your callback function is always thread-safe (for example, using atomic operations, mutex etc).