In the Big Denominator ecosystem, an application is a directed graph – a collection of models (the graph nodes) that communicate through the exchange of data (the graph edges). Each node or model is defined by its input and output data. This data, wrapped in a single structure – which we refer to as the model’s schematic, is the model’s API.

(For more information on schematics, particularly defining them, see here.)

Nodes operate on their schematics by using the input fields to fill out and populate the output fields. This operation is defined in a single method called Process. Defining the logic inside the Process routine is the sole responsibility of the developer: how to translate inputs into outputs. The Big Denominator DGE (Directed Graph Engine) will continually funnel schematic instances to their specific node and use the Process routine to operate on them.

Defining and writing a node is almost as simple as defining a schematic; it boils down to writing a single method. Consider the encryption model we outlined in the Schematics post. The following code defines that model:

DEF_NODE➀(MyEncryption➁, EncryptPkt➂, ()➃, ()➄, ()➅)
{
     PROCESS_STATUS Process(EncryptPktPtr inp_sSchematicPtr) ➆
     {
         [model logic] ➇
     }
}
  1. DEF_NODE keyword identifies that a node is being defined.
  2. MyEncryption is the name assigned to the node.
  3. EncryptPkt is the schematic for which this node is responsible.
  4. Enclosed in parentheses, a comma-delimited list of schematics the node will request (see below).
  5. Enclosed in parentheses, a comma-delimited list of schematics the node will send (see below).
  6. Enclosed in parentheses, a comma-delimited list of schematics the node will view (see below).
  7. Process method signature taking a smart (shared) pointer to an instance of the model’s schematic and returning a status indicator: success, failure, or (in the case of asynchronous design) pending the completion of a callback.
  8. The ACTUAL model logic.

In order to help translate inputs into outputs, nodes have access to a limited (by design) yet powerful API to the rest of the system.

  • Request: A model can request data from another model (possibly even itself). Simply fill out an instance of the other model’s schematic and call Request. This method has both synchronous (“ I can wait for the model to complete”) and asynchronous (“I’m going to keep working; when the model finishes, here’s my callback”) versions.
  • Send: A model can output data for downstream processing. Contrary to Request, Send does not return back any indication as to whether the downstream processing was successful. This is typically how output data is generated: a model will Send data that is then picked up by an output feed.
  • View: A model may view an instance of a schematic: the model is given non-exclusive access to the first available instance. This is typically used for configuration data.

Suppose our encryption model requires a random number, itself generated by another node. Suppose the random number generator's schematic is defined as follows:

SCHEMATIC(RandomNumbers, 9999, (double dRandom;), (dRandom))

Using this definition, our encryption model can request the random number in its Process routine.

PROCESS_STATUS Process(EncryptPktPtr inp_sSchematicPtr)
{
     CSmartPtr<RandomNumbers> myRandomPtr(CreateSmart<RandomNumbers>()); ✪
     if (!Request(myRandomPtr)) { return PROCESS_STATUS::FAIL; }

     [additional logic]
}

The Request / Send / View API requires the use of smart (shared) pointers. Their use is fundamental to memory managment and to Big Denominator's ability to efficiently shuttle data between nodes. The instance of the smart pointer is created using the CreateSmart facility.

Note however that we will not be able to compile our node with this definition of the Process method. In requesting data from the random number generator, we violated the implicit contract stipulated in above: we said we were not requesting anything. Instead, our code must look like the following:

DEF_NODE(MyEncryption, EncryptPkt, (RandomNumbers), (), ())
{
     PROCESS_STATUS Process(EncryptPktPtr inp_sSchematicPtr)
     {
         CSmartPtr<RandomNumbers> myRandomPtr(CreateSmart<RandomNumbers>());
         if (!Request(myRandomPtr)) { return PROCESS_STATUS::FAIL; }

         [additional logic]
     }
}