Process

metronom

Sequences of actions, such as the body of a group, of a whenever, of a loop, etc., are the specification of a thread of execution1 owning its own temporal properties and managed independently by the run-time. An Antescofo process simply abstracts this notion by parameterizing a sequence of actions. A process can be instantiated multiple times by giving a value to the parameters (aka process call).

Processes are similar to functions: after its definition, a function can be called to compute a value from its body (an expression). After its definition, a process can be called and run from its body which is a group of actions. This group is called the instantiation of the process. Notice that there can be several instantiations of the same process that run in parallel.

Processes can be defined using the @proc_def construct. For instance,

          @proc_def ::trace($note, $d)
          {
                 print begin $note
              $d print end $note
          }

The name of the process denotes a proc value (see Proc Values), and is used to call the process.

Calling a Process

A process call is a mechanism similar to a function call: its parameters are expressions listed between parentheses and their values are bound to the arguments before running the body.

A process can be called as an action or as an expression.

Calling a Process as an Action

Here, calling a process is an action:

          NOTE C4 1.3
            ::trace("C4", 1.3)
            ; more actions
          NOTE D3 0.5
            ::trace("D3", 0.5)

In the previous code, the process is referred to through its name, a ::-identifier, so it is apparent that a process is called. But the call can also be indirect, as in:

          NOTE C4 1.3
            $x := ::trace
            :: $x ("C4", 1.3)  ; calling the process assigned to $x

the process ::trace is a value (of type proc) assigned to variable $x. To call the process refered by $x, we use the syntax :: expression (...) where expression must evaluated to a proc.

A process call is a compound action equivalent to the insertion of the process body at the place of call as a group. So the previous code fragments launch the following behavior:

          NOTE C4 1.3
             group trace_body1 {
                   print begin "C4"
               1.3 print end "C4"
             }
             ; more actions
          NOTE D3 0.5
              group trace_body2 {
                   print begin "D3"
               0.5 print end "D3"
             }

Calling a Process as an Expression

A process can also be called in an expression with the same syntax. The instantiation mechanism is similar: a group starts and runs in parallel. However, an exec value is returned as the result of the process call. See section exec value. This value refers to the running group lauched by the process instanciation. It can be used in the computation of the surrounding expression.

The exec value corresponding to the process instance can also be accessed within the process body itself through a special variable

          $MYSELF

This variable is read-only and is managed by the run-time.

Calling a process in an expression does not require the :: marker, that is the expression: antescofo $x(1, 2, 3) can be either a process call or a function call, depending on the value of $x. It causes no trouble. The :: keyword is mandatory only in actions, because allowing the syntax of function calls would lead to ambiguities in the specification of actions.

Recursive Process

A process may call other processes and can be recursive, calling itself directly or indirectly. For instance, an infinite loop

          Loop L 10
          {
               ; actions ...
          }

is equivalent to a call of the recursive process defined by:

          @proc_def ::L()
          {
               Group iterate { 10 ::L() }
               ; actions ...
          }

The group iterate is used to recursively launch the process without disturbing the timing of the actions in the loop body. In this example, the process has no arguments.

Process as Values

A process definition is a proc value and can be the argument of another process. For example:

          @Proc_def ::Tic($x) {
               $x print TIC
          }

          @proc_def ::Toc($x) {
               $x print TOC
          }

          @proc_def ::Clock($p, $q) {
               :: $p(1)
               :: $q(2)
             2 ::Clock($p, $q)
          }

A call ::Clock(::Tic, ::Toc) will print TIC one beat after the call, then TOC one beat after the latter, and then again at date 3, at date 4, etc.

Aborting a Process

The actions spanned by a process call constitute a group. It is possible to abort all groups spanned by the calls to a given process using the process name:

          abort ::P

will abort all the active instances of ::P. The active instances of the process are the instantiation of the process body that are still alive.

It is possible to kill a specific instance of the process using its exec value:

          $p1 := ::P()
          $p2 := ::P()
          $p3 := ::P()
          ; ...
          abort $p2  ; abort only the second instance
          abort ::P  ; abort all remaining instances

Using the special variable $MYSELF , it is possible to implement self-destruction on a specific condition e:

          @proc_def ::Q()
          {
                @local $PID
                $PID := $MYSELF
                ; ...
                whenever (e) { abort $PID }
                ; ...
          }

The value of $MYSELF is stored in the local variable $PID because the value of $MYSELF is the exec value of the immediatly surrounding group. If we replace$PID with $MYSELF in the whenever, we kill the running instance of the whenever body, not the process instance.

Processes and (local) Variables

Processes are defined at the top-level. So, the body of the process can only refer to global variables and to local variables introduced in the body. The process parameters are implicit local variables but other local variables can be introduced explicitly using the @local statement.

Process parameters are local variables

The parameters of a process are local variables that are initialized with the value of the arguments given in the process call. These variables are local to the process instance. These variables can bet set in the process body, as in:

         @proc_def ::P($x)
         {
               whenever ($x == $x) {
                   print "$x = " $x
               }
               ; ...
               1 $x := $x + 1
         }

One beat after the call ::P(0), the text $x= 1 appears on the console: the variable $x local to this process instance has been incremented by one which has triggered the whenever outputting the message. We stress that values are passed to the process, not variables or expressions, which is sometime a cause of pitfalls, see paragraph What to Choose Between Macro, Functions and Processes.

Other than the initialization, there is no difference at all between the process parameters and a local variable introduced explicitly using @local statement.

Local variables

Variables that are defined @local to a process are defined per process instance: they are not shared with the other calls. One can access a local variable of a specific instance of a process through the exec value of this instance, using the dot notation:

          @proc_def DrunkenClock()
          {
               @local $tic
               $tic := 0
               Loop (1. + @random(0.5) - 0.25)
               { $tic := $tic + 1 }
          }
          $dclock := ::DrunkenClock()
          ; ...
          if ($dclock.$tic > 10)
          { print "Its 10 passed at DrunkenClock time" }

The left hand side of the infix operator . must be an expression whose value is an active exec. The right hand side is a variable local to the referred exec. If the left hand side refers to a dead exec (cf. exec), the evaluation of the dot expression raises an error.

In the previous example an instance of ::DrunkenClock is recorded in variable $dclock. This exec is then used to access to the variable $tic which is local to the process. This variable is incremented with a random period varying between 0.75 and 1.25.

Accessing a local variable through the dot notation is a dynamic mechanism and the local variable is looked first in the instance referred by the exec, but if not found in this group, the variable is looked up in the context of the exec, i.e. in the instance of the group that has spanned the process call, and so on, climbing up the nesting structure (in case of recursive process, this structure is dynamic) until the variable is found. If the top-level context is reached without finding the variable, the <undef> value is returned and an error message is issued.

Dynamically Scoped Variable

This mechanism is useful to dynamically access a variable defined in the scope of the call. For example:

        @proc_def ::Q($which) { print $which ": " ($MYSELF.$x) }

        $x := "x at top level"

        Group G1 {
              @local $x
              $x := "x local at G1"
              ::Q("Q in G1")
        }

        Group G2 {
              @local $x
              $x := "x local at G2"
              ::Q("Q in G2")
        }

        ::Q("Q at top level")

will print:

    Q in G1: x local at G1
    Q in G2: x local at G2
    Q at top level: x at top level

Assignment using the dot notation

The reference of an instance of a process can be used to assign a variable local to a process from the outside, as for example in:

        @proc_def ::P()
        {
             @local $x
             whenever ($x} { print "$x has changed for the value " $x }
             ; ...
         }
         $p := ::P()
         ; ...
         $p.$x := 33

the last statement will change the value of the variable $x only for the instance of ::P launched at line 7 and this will trigger the whenever at line 4.

Notice that the features described here specifically for a process instance work for any exec (see sections Action As Expression and exec value).

Process, Tempo and Synchronization

The tempo of a process can be fixed at its definition using a tempo attribute:

          @proc_def ::P() @tempo := ...
          { ... }

In this case, every instance of ::P follows the specified tempo. If the tempo is not specified at the process definition, then the tempo of an instance is implicitly inherited from the call site (as if the body of the process was inserted as a group at the call site).

For example:

          Group G1 @tempo :=  60 { Clock(::Tic, ::Tic) }
          Group G2 @tempo := 120 { Clock(::Toc, ::Toc) }

will launch two clocks, one ticking every second, the other one tocking two times per second.

If not explicitly specified, the tempo of a process instance is inherited from the call site. This is also true for the other synchronization attributes (targets, strategies, etc.).

Actors

Actors are built on processes to provide autonomous objects that can react to external signals and able to answer requests initiated from other program parts. Actors do not belong to the Antescofo core: they do not add fundamental mechanisms, they rather provide syntactic sugar for the sake of the programmers and they are internally rewritten as processes. They are described in chapter actors.



  1. See the notion of exec and coroutine. A sequence of actions may span multiples threads, e.g. the body of a loop or of a whenever