Next: Foreign Component, Up: Code Integration [Contents][Index]
This chapter describes the C++
code that is generated by Dezyne
and the integration thereof.
Every wellformed Dezyne model can be automatically converted into a corresponding wellformed C++ representation. This means that the generated code will compile without compilation errors. A verified Dezyne model, once converted into a corresponding C++ representation, exhibits the same behavior when executed as can observed in the Dezyne simulation and verification of the model.
In Dezyne there are three model types: interface, component and system.
In this chapter we cover the code which is generated from these models as well as the way the generated code might be integrated.
Dezyne turns an interface such as:
interface some_interface { in void in_event(); out void out_event(); behavior { on in_event: out_event; } }
into a C++ class representation similar to this:
struct some_interface { struct { dezyne::function<void ()> in_event; } in; struct { dezyne::function<void ()> out_event; } out; };
Each event in an interface is a slot to which a value of something with
the appropriate callable signature can be assigned. A callable value in
C++ is either: A function pointer or a functor (an object implementing
the function ::operator ())
, like a C++11 lambda. For example:
void foo () {} some_interface port; port.out.out_event = foo; port.in.in_event = port.out.out_event;
Note that the last statement above short circuits the in_event to the out_event as is described in the Dezyne interface.
One could consider a component to be no more than the connecting part between all of its ports. For example:
import some_interface.dzn; component some_component { provides some_interface provided_port; requires some_interface required_port; behavior{} }
in which case a simplistic C++ representation could look like this:
struct some_component { some_interface provided_port; some_interface required_port; some_component () : provided_port () , required_port () { provided_port.in.in_event = dezyne::ref (required_port.in.in_event); required_port.out.out_event = dezyne::ref (provided_port.out.out_event); } };
Note that dezyne::ref allows short circuiting events which will be initialized at a later stage.
However, this representation does not implement the semantics of Dezyne (see See Execution Semantics). In order to achieve this, the Dezyne runtime manages the event exchange between components. And of course for all practical purpose and intent one expects a component behavior to be more complicated to be able to comply with all of its interface behaviors.
Along the same lines a Dezyne system may aggregate other components and systems and bind them together by their ports. For example:
import some_component.dzn; component some_system { provides some_interface provided_port; requires some_interface required_port; system { some_component top; some_component middle; some_component bottom; provided_port <=> top.provided_port; top.required_port <=> middle.provided_port; middle.required_port <=> bottom.provided_port; bottom.required_port <=> required_port; } }
or depicted in a diagram:
Constructing such a system using Dezyne is straightforward. Every model can be automatically converted into code and by the hierarchical nature of Dezyne all components and systems slot together automatically, however two facilities are required to allow this: the dezyne runtime and the dezyne locator. Both are provided by Dezyne.
In C++ the main function for this system might look like this:
#include "some_system.hh" #include "dezyne/runtime.hh" #include "dezyne/locator.hh" int main() { dezyne::locator loc; dezyne::runtime rt; loc.set (rt); //construct the system some_system system (loc); //connect the outer events directed at the system system.provided_port.out.event = []{ std::cout << "system.provided_port.out.event" << std::endl; }; system.required_port.in.event = []{ std::cout << "system.required_port.in.event" << std::endl; }; //and finally fire some of the external events system.provided_port.in.event (); system.required_port.out.event (); }
Runtime: The runtime takes care of decoupling the events between the caller and the callee when this is required.
Locator: The locator allows injecting the implementation behind a port deep into the system from the outside.
In the example you can see that the locator facility is also responsible for passing an instance of the runtime into the system. Injection example:
interface Foo { in void bar(); behavior { on bar:{} } } component some_component2 { provides some_component2 provided_port; requires injected Foo required_port; behavior { /* ... */ } }
int main() { dezyne::locator loc; dezyne::runtime rt; loc.set (rt); Foo foo; foo.in.bar = []{/*no op*/}; loc.set (foo); some_component comp (loc); comp.provided_port.in.in_event (); }
Next: Foreign Component, Up: Code Integration [Contents][Index]