Overview
Objects, Components and Circuits
One of the most important concepts in Blueprint development is the 'Circuit'. Most developers will be familiar with the concept of a 'Class' in an Object-Oriented sense, and CDL's circuits are notionally similar to OO classes in a number of ways;
As well as supporting concurrent execution, one of the prime goals of Blueprint is to 'encapsulate', 'archive' and 're-use' program logic (algorithms and infrastructure). As with classes, circuits can be prototyped in 'declarations', have 'definitions' that are analogous to 'class bodies', support public and private data, and can be instantiated in a nestable manner. That is to say that sub-circuits can be members of aggregating circuits, and so on. All CDL components have a concept of 'dimensionality' and Blueprint provides a 'connection wizard' that automates most multidimensional connection.
Since circuits, like classes, have a concept of state; there is no limit to the number of named circuit instances that can be created. Most circuits are arbitrarily compose-able (subject to prototype compatibility) and so the complex array output of an FFT for example, can be directly connected to the input of a complex array filter, an RSS feed adaptor component could be connected to any number of business processes, and so on.
The Differences Between Circuits and Classes
Whilst class instances are passive entities that are 'executed' by one or more threads, circuit instances can be thought of as active entities that execute asynchronously and synchronize through their connections. There is no limit to the concurrency that a circuit component can have. If a multi-dimensional component has multi-dimensional member components then the resulting concurrency will be the product of the two component concurrencies. If the application's synchronization logic permits two or more components to execute simultaneously then the resulting concurrency will be the sum of the two or more component concurrencies, and so-on. Blueprint applications are also interoperable with bottom-up multi-core technologies like TPL or TBB.
Circuits enable encapsulation of concurrent code in much the same way as classes encapsulate sequential code. By providing encapsulation, circuits allow you to re-use pieces of circuitry quickly and easily without having to worry about the internal details of the circuitry. The same guidelines apply to circuits as to classes and you should always aim to create cohesive and re-usable components.
Definitions vs. Instances
There are two distinct views of a circuit:
- What the developer of the circuit component sees
- What the end user of the circuit component sees
The developer of a component defines the interface to that component (its prototype) and the implementation of that component (its definition). The end user of a component creates an instance of or a reference to an instance of the component and then connects it into their circuitry.
There is an analogy here with classes:
- A class header corresponds to a circuit prototype
- A class implementation corresponds to a circuit implementation
- A class instance corresponds to a circuit instance
- A class reference or pointer corresponds to a circuit reference
Circuit Definition
The following example shows a simple circuit definition containing a fragment of circuitry:
There are several objects here that have the same dimensionality and these can be factored out into a separate circuit definition:
Circuit Instance
Having separated the multidimensional objects into a separate definition, that definition must be instantiated and connected in the previous definition in place of the multidimensional objects:
Circuit Reference
Object references are provided as one means of enabling object instances to connect to remote object instances in adjacent circuits. The referenced object must be exposed by a circuit connection pin (see circuit prototypes). A reference is equivalent in many ways to a pointer, but one which is valid at network scope. They are particularly useful for applications that require a Service Oriented Architecture (SOA) approach, and allow applications to 'call' remotely executing circuits. Examples could include a matrix inversion service running on a remote specialized platform, or a service that executes on a machine that has particular device access.
Object references are 'logical' entities and do not make any assumptions about the physical location of the referenced object. At runtime, objects are registered and located by name, and so CDL applications can be re-accreted to any process topology, and executed on any connected machine, without modification.
The reference has three components. The first is the target circuit's 'type' which is used to identify its connection prototype, the second is a path to the referenced circuit instance, and the third identifies the particular circuit connection pins that expose the referenced member objects. The example below references a sub circuit of type 'MyCct', which has instance name 'CctA/CctB'. The 'Write' method delivers input, and the 'Read' method receives output.
There are two referencing schemes. In the first case circuits can be identified by an absolute path, and in the second they can have a relative path. This is similar to the two options provided by most filing systems and reflects the fact that circuit hierarchies are very similar to directory structures. In this context circuits can be thought of as directories (or folders) and their member objects can be thought of as sub-directories or files.
Note that it must always be the instance that connects to the reference as a reference can't own connections. As all connections are made by the consumer (provider volition connections will be supported in a future release), it is not possible to connect to a consumer pin on a circuit reference.
Absolute Paths
Absolute paths are distinguished from relative paths by the inclusion of a preceding forward slash '/' character. If the '/' character is excluded then the path is assumed relative to the referencing circuit. This means that a reference that has a single name and no preceding '/' is actually referring to a sub-circuit instance as opposed to a remote object instance.
If a circuit with instance name "CctB" has a top level parent with instance name "CctA" then its path would be "CctA/CctB". If it owned a sub-circuit with instance name "CctC" then that circuit's path would be "CctA/CctB/CctC". If it owned a sub-circuit with instance name "/CctC" however, then that circuit's path would just be "/CctC".
Absolute paths are seldom used between sub-components of re-usable components because they assume that the component has been instantiated in a particular place. They are however, usually used for references to 'services' that are 'installed' in particular places.
Relative Paths
In order to specify a remote reference using the relative path scheme, CDL provides the carat '^' operator which is equivalent to the '..' double dot operator adopted by most filing systems. So "^CctA" is equivalent to "../FileA" in that it refers to an object in an adjacent branch of the structure, that exists at the same level. Carat '^' operators can be combined and so '^^' refers to referencer's parent's parent (grandparent).
References are further distinguished from sub-circuit instances by their shaded backgrounds. As pointed out above, a reference that contains a single name without a preceding '/' character is equivalent to a sub-circuit, but by convention, the sub-circuit does not have a shaded background. This is adopted for readability.
Examples
The figure above shows a a circuit definition of type {MyCct} that contains a sub-circuit instance of type {SubCct} and two remote references to circuit instances of types {AbsRefCct} and {RelRefCct} respectively. Note that the two references are distinguished from the "CctA" instance by their shaded backgrounds. In the case of the references, the connections are actually made to remote instances that are identified by their respective path specifiers.
This example illustrates two distinct referencing schemes. In the first case 'absolute referencing' is used and this is apparent from the preceding '/' in the reference circuit path "/CctB". This identifies the unique sub-circuit instance "/CctB" which could be instantiated anywhere. The benefit of this scheme is that instances of {MyCct} can be created anywhere within the circuit hierarchy, and will connect to the referenced "/CctB" sub-circuit instance regardless.
In the second case 'relative referencing' is used. The relative path "^CctC" will resolve to the "CctC" sub-circuit of the {MyCct} instance's parent. So if for example, {MyCct} were instantiated with path "CctD/CctA", then the relative reference would resolve to "CctD/CctC". In this case CctA and CctC would actually be siblings (common parent).
Circuit Prototype
A circuit prototype defines the allowable connections that can be made to/from particular pins on a circuit definition when it is instantiated. The following diagram shows an example of a prototype for a matrix processor circuit that specifies that its output event consists of a single store with record type MatrixT.