Workspace 6.21.5
|
In this section, we're going to understand the code that was automatically generated by Workspace so that we know how to customise our operation, and define its execute()
function: the function that all operations use to do their work.
Creating and running a simple test workflow (continue on with tutorial Writing a Workspace Polymorphic Data Operation )
Again, the header file should contain a header guard followed by any other header files this one requires. The Workspace/DataExecution/Operations/Builtin/polymorphicdataoperation.h header provides the DataExecution::PolymorphicDataOperation base class that we have to inherit from. The Workspace/DataExecution/Operations/operationfactorytraits.h header provides the DECLARE_WORKSPACE_OPERATION_FACTORY macro which is the Workspace operation analogy of the DECLARE_WORKSPACE_DATA_FACTORY macro for Workspace data types.
The next thing to do is to define our implementation class CalculateShapeAreaImpl
.
For the purpose of this tutorial, we will create an operation that takes a particular shape as input and provides the area as an output. The class is defined like this:
There are a few things to note in this class definition. Firstly, there is no sign of any inputs or outputs anywhere. This is deliberate, because we don't want other code to know about how the operation does what it does, nor what data it stores internally. These are implementation details that should be hidden, which is why we have the CalculateShapeAreaImpl
implementation class. We will define CalculateShapeAreaImpl
in the calculateshapearea.cpp
file shortly, but we don't need to know anything about it for now because we only store a pointer to a CalculateShapeAreaImpl
object, which we call pImpl_
. This is a common name given to this sort of "pointer to implementation" object within Workspace.
The second thing to note is that, apart from the constructors and destructor and the execute()
function, this class has three additional methods canChangeDataFactory()
, canChangeDataName()
and dataObjectChanged
. We will talk some more about this shortly.
At the end of the file is the DECLARE_WORKSPACE_OPERATION_FACTORY macro. Just like its analogous macro for Workspace data types, this macro defines the things needed by Workspace when it uses the factory. The first parameter is the fully scoped name of the operation and the second parameter should be the export symbol used between the class
keyword and the class name in the class definition.
Creating and running a simple test workflow (continue on with tutorial Writing a Workspace Polymorphic Data Operation )
We will show this implementation file in stages and talk about each part as we go. As usual, we start by including the required headers. This time, we have a few more that we need. For now, we'll just list them and hold off talking about them until we meet the things they define for us. We also include the usual namespace scopes and make things from the CSIRO::DataExecution namespace visible to make our code clearer:
The next thing we need to do is define our private implementation class:
The SimpleOutput class is analogous, provided by the same Workspace/DataExecution/InputOutput/simpleoperationio.h header and with area_
acting as a wrapper around the area data object.
So: Inputs
and Outputs
wrap the data, giving it a name and exposing it to Workspace.
The definition of the CalculateShapeAreaImpl
constructor makes this clearer:
The first parameter to area_
provides the name the input/output will be known by and the second parameter is the operation to add the input/output to.
For shape_, which is of type InputScalar and is a polymorphic input, the first parameter again provides the name the input will be known by, the second parameter is a reference to the underlying data object, the third is the operation to add the input/output to and the last parameter indicates if the input can be modified in place.
As you can see, all we have needed to do was add to our auto-generated execute
function is:
The InputScalar class is provided by the Workspace/DataExecution/InputOutput/inputscalar.h header. InputScalar
acts as a wrapper around a data object and presents it to an operation as a single named input. In the above example, shape_
will act as a wrapper around radius_
.
So: DataObjects
point to our data, and Inputs
and Outputs
wrap the data, giving it a name and exposing it to Workspace.
The CalculateShapeArea
constructor and destructor come next:
The CalculateShapeArea constructor passes several important pieces of information to its base class. First, it passes the factory associated with the operation in the OperationFactoryTraits<CalculateShapeArea>::getInstance()
parameter. The second parameter specifies the default label to give to all new operations. This will be shown to the user and can contain more or less any text, although shorter is better. Spaces are allowed in the label, as the "Calculate shape area"
example shows. The next parameter specifies the default data factory that this operation uses. In this example, the shape_ input is initialised to a Circle
.
The constructor creates the private implementation object pImpl_
. The destructor simply deletes what the constructor created.
Then we define the execute()
function:
The PolymorphicDataOperation
has a few pure virtual functions. We need to define these in our class.
The above code restricts the polymorphic data input shape_
to Circle, Square or Rectangle. Other data types are not permitted and will return false
. If the operation's polymorphic data input/output allows all data types, then the function is greatly simplified and the programmer only needs to return true. For example:
canChangeDataName
returns true if the subclass allows the data name to be set to name. The subclass can return false to indicate that the data name cannot be changed to name, or indeed that it cannot be changed at all.
The default implementation of this function (in the base class) does nothing. This function is called by setDataFactory once the data object has been changed. It allows the subclass to take additional action to update itself before the process of changing the data factory is completed. For example, changing the polymorphic data type from Circle to Square triggers a call to this function and shape_
can be set to the new data object Square.
The last thing we do is close off our namespaces and use a macro to define the factory for our operation:
The DEFINE_WORKSPACE_OPERATION_FACTORY macro is just like the one we used for the data factory. It takes care of defining the operation factory for us. We have to supply three things to the macro: the operation it is for (CalculateShapeArea
), the plugin it belongs to (SimplePlugin::getInstance()
) and a category to file it under ("Simple plugin"
in this example).
Creating and running a simple test workflow (continue on with tutorial Writing a Workspace Polymorphic Data Operation )
The operation creator wizard has automatically updated the simpleplugin.cpp file so that the plugin exposes the CalculateShapeArea operation to Workspace. Two changes have been made to this file, the first of which is to include the new operation header at the top.
Secondly, a line of code has been added to the SimplePlugin::Setup function. This line will register our new OperationFactory (an auto-generated class which knows how to create our new operation) with Workspace, so that it can create CalculateRectangleArea operations as the user requests them.
Creating and running a simple test workflow (continue on with tutorial Writing a Workspace Polymorphic Data Operation )
Lastly, the operation creator wizard has automatically updated the plugin CMakeLists.txt file to add the operation's header and source files in the three set functions:
Creating and running a simple test workflow (continue on with tutorial Writing a Workspace Polymorphic Data Operation )