State machines in Visual FoxPro

 

Josip Zohil, Koper, Slovenija, july 2013

 

It is difficult to write parallel and asynchronous code in Visual FoxPro (VFP). In this article I shall describe some problems that can be solved in parallel and/or in asynchronous way. In the article Trampoline and asynchronous execution in VFP I have presented a trampoline method to fetch WEB resources in an asynchronous way; without blocking. In this article I shall illustrate how to parallelize execution using a state machine method.

 

State machine example

 

Let us start with a simple example. We have to write the names of two kids (LIAM and TARA) on the screen. They are concurrent, to avoid problems, we accept to write their names on the screen in parallel. We have used  the same techniques as when we manage them on a trampoline, for example: three bounce LIAM and after that three TARA.

The names have to be visible on the screen, so we shall write LIAM's name horizontally and TARA's name vertically.

function forcey()

ACTIVATE screen

DO WHILE .t.

genprinty()

If MOD(i,tasklen+1)>=tasklen  && test stop

exit

ENDIF

enddo

endfunc

Function genprinty() && closure on i

i=i+1

*kmes=kmes+1

@24-MOD(i,23),MOD(j,80)+1 say xname

Endfun

 

We shall use a function genprintx () to print LIAM's names vertically (y coordinate). The screen has 24 rows and 80 columns. This is a generator function (see, Closures and generators in VFP). Using a closure it takes the values for i and xname from the environment.

We force genprintx() to be printed for tasklen times and then stop.

 

A similar pair of functions we have for concurrent TARA:

Function forcex()

ACTIVATE screen

DO WHILE .t.

genprintx()

If MOD(j,6*(tasklen+1))>=6*tasklen  && test stop

exit

ENDIF

enddo

endfunc

Function genprintx()

j=j+6

@1+MOD(i,23),1+MOD(j,80) say yname

Endfun

A function genprinty print TARA names horizontally. For a moment do not be confused with the MOD function. Suppose the screen coordinate behaves in a correct way.

Let us create a manager using a state machine with these states:

Start -> LIAM -> TARA-> ... STOP

After the initialization phase we start with a state LIAM, force genprinty to print, for example three times, after that we pass to state TARA and force genprintx to print three times. We pass from state to state for 20 times  and then we stop (pass to state stop).

The coordinator is a function statemachine:

FUNCTION statemachin()  && three states

xname="LIAM"    &&  Print (bounce) LIAM

yname="TARA"   &&  Print (bounce) TARA

stopstate=.f.  && stop print (bounce )

i=-1

j=-1

k=0

state = xname  &&start state

*pred="stopstate"

tasklen=3  && print 3 times

tasklen=tasklen-1

*DIMENSION mes(3)

DO WHILE .t.

   newState = state

*     kmes=0

   DO case

     CASE  state=xname

      forcey()  && print xname for tasklen times

      newState = yname

      IF NOT stopstate

 *     SELECT mess

 *     stopstate=mess.stop

      ENDIF

     CASE state=yname

      forcex()   && print yname for tasklen times

      newState = xname

     OTHERWISE

     ENDCASE

     k=k+tasklen+1

     state=newstate

     WAIT  state wind TIMEOUT 0.5

     IF k>60  && 60/3=20

       stopstate=.t.

     endif

     IF stopstate

       EXIT

     endif

enddo

ENDFUNC

 

In the VFP command window we run:

statemachin()

and the program starts writing on the screen: three times LIAM and three times TARA. The command:

WAIT  state wind TIMEOUT 0.5

Pauses the program execution for a half of a second. The program bounce LIAM and TARA for 20 times on a trampoline.

 

Serial execution

 

Let us compose the two functions forcex and forcey to execute in a serial order: first we run forcey for 20 times and than forcex 20 times.

We create a function:

Function statemachinser(N,funexy,predicate)

xname="LIAM"   

yname="TARA"  

stopstate=.F.

i=-1

j=-1

k=0

*pred="stopstate"

tasklen=N

tasklen=tasklen-1

*DIMENSION mes(3)

Do While .T.

      Eval(funexy)

      If funexy="forcex()"

            i=i+1

      Else

            j=j+6

      Endif

      k=k+tasklen+1

      Wait  "working" Wind Timeout 0.5

      If k>20*N

            stopstate=.T.

      Endif

      If Not Evaluate(predicate)

**stopstate

*     SELECT resource

*   stopstate=stop

      Endif

      If Evaluate(predicate)

*stopstate

            Exit

      Endif

Enddo

Endfunc

 

And we execute it:

Clear

statemachinser(3,"forcey()","stopstate")

statemachinser(3,"forcex()","stopstate")

This program writes on the screen 20 times LIAM and after that 20 times TARA.

1)      First user's observation, TARA is not satisfied with this serial execution; its name appears too late on the screen.

2)      Second, expert observation: In case, there is an error in the function forcey, when i=13, we shall have written on the scree only LIAM names; Using a statemachine program, we shall have twelve LIAM and twelve TARA, a better »production«.

 

Generators are composable; it is better if we compose them in parallel.

 

State machine or trampoline

 

We can write a state machine program as a trampoline (see Trampolines and asynchronous execution in VFP):

Function trampmachin(N,predicate,First,Second)

xname="LIAM"    && A, three states, print (bounce) LIAM

yname="TARA"   && B   Print (bounce) TARA

stopstate=.F.  && C, stop print (bounce )

i=-1

j=-1

k=0

*pred="stopstate"

tasklen=N

tasklen=tasklen-1

*DIMENSION mes(3)

Do While .T.

     Evaluate(First)

*kmes=0

      If Evaluate(predicate)

            Exit

      Endif

      Evaluate(Second)

      k=k+tasklen+1

*  state=newstate

      Wait "working"  Wind Timeout 0.5

      If k>N*20

            stopstate=.T.

      Endif

      If Not stopstate

*     SELECT resource

*   stopstate=stop

      Endif

      If Evaluate(predicate)

            Exit

      Endif

Enddo

Endfunc

 

A program trampmachine is  similar to  statemachine; it is a little bit simpler. We run it in a VFP command window:

Clear

trampmachin(3,"stopstate","forcey()","forcex()")

This program has four arguments. Inside the program is only logic: a composition of two functions and a predicate to stop execution. We can pass to the program trampmachine other arguments, for example we run:

trampmachin(1,"stopstate","forcey()","forcex()")

In the last example concurrency for the kids was resolved with »one bounce on a trampoline«.

You can organize also more kids on a trampoline: the programming technique remains the same.

In VFP we mostly coordinate generators and a more natural way is to manage them using a trampoline technique.

 

Serial to parallel

 

A function statemachinser is written to be executed serially. It is similar to a trampmachine function (parallel execution), so it is easy to write a function that executes in parallel, when we have a model that execute serially.

 

Asynchronous execution

 

In the next example we shall show the execution of the function ststemachin() is asynchronous. The process forcey() or LIAM is producing messages and the process forcex() is  receiving these messages in an asynchronous (non blocking) way. forcey() is producing messages, when it produces a message it continue its work. The second process forcex receive these messages and continue its work.

In the next example a process LIAM will publish messages about the coordinate of his jumps (three jumps with three coordinate x and y). A process TARA will be a subscriber to these messages, after receiving them she will write the coordinates on the screen.

We modify the function statemachin(); we remove the star (*) before the command DIMENSION mes(3), and kmes=0. Remove also the star before kmes=kmes+i in the function genprinty and genprintx:

Function genprinty() && closure on i, producer

i=i+1

kmes=kmes+1

*generate a message, (an array component or a field in a table)

mes(kmes)=STR(24-MOD(i,23),2)+","+STR(MOD(j,80)+1,2)

@24-MOD(i,23),MOD(j,80)+1 say xname

Endfun

Function genprintx()  &&closure on j, consumer

j=j+6

kmes=kmes+1

*consume a message in an asynchronous way

@1+MOD(i,23),1+MOD(j,80) say mes(kmes)

Endfun

 

When you run a modified statemachin it will display three times a LIAM name and instead of TARAs' names three pairs of  the coordinates of LIAMs' jumps. The data are presented in parallel on the screen.

A modified program is a non blocking and asynchronous code: the LIAM process executes, pauses, TARA process writes the received data on the screen and so on.

A mes array is a message queue. A LIAM process puts in it three messages and a TARA process consumes them.

 

Table as a message channel

 

The difference between a synchronous and asynchronous execution is also the possibility to cancel (stop) the asynchronous process. Let us write an example of passing messages using a VFP table. We have a data base table mess with a field (column) stop L (logical), and one record. We shall use this table as a message channel to send messages to our state machine. In the program statemachin remove the stars (*) in the lines:

SELECT mess

stopstate=mess.stop

Before starting the statemachin we open the table:

Select 0

Use mess

clear

Statemachin()

On another computer we open a table mess and we replace a field stop with .T.. A statemachine program will stop before executing all the 24 loops.

 

Conclusion

 

A state machine is a technique to coordinate functions (programs) execution. We have an interest in compositions that allow us to inject  code. These are possible when a coroutine (function, process) stops and later resume. We mostly coordinate generators. I have presented two techniques state machine and trampoline to coordinate VFP procedures. Generators are composable, so you can create entire application using these techniques. My opinion is, the best way is to combine them with the VFP event loop.