Animation and communication state transitions can be easily described using JS and Python generators.

I think that programs often consider state transitions in which the state of the program changes depending on the situation.

flowchart

Patterns that tend to be state transitions in asynchronous processing

In a normal program, you can write a state transition by writing the program procedurally in order from the top, but in programs around animation, games, communication, you have to do other processing while waiting for the completion of hardware and user input. There are many things that I can't write like that.

You may have experience writing code with this kind of atmosphere.

//Occasional information about user input and frame updates
var message = pullMessage()

//Sort the destination of the message for each state
switch(mode) {
    case "ANIMATION_START":
       start(message)
       mode = "ANIMATION_FRAME1" //Transition to FRAME1 at next input
    break;
    case "ANIMATION_FRAME1":
       if(frame1(message)){
           mode = "ANIMATION_END"
       } else {
           mode = "ANIMATION_START"
       }
    break;
    case "ANIMATION_END":
       end(message)
       mode = "ANIMATION_START"
    break;
}

The above program is difficult to handle in the following aspects.

I'm not convinced that state transitions that can be written very easily with synchronous programming become so complicated.

So the generator is also a coroutine

Python and JavaScript have a feature called generators. The functions are as follows.

def generator():
    yield 2
    return
function* generator() { 
  yield 1;
  return
}

Python and JavaScript have almost the same usability.

What is a coroutine?

It seems that the function called a generator has been called ** asymmetric coroutine ** since ancient times. What a coroutine is is a function that behaves as follows.

coroutine

Although the two coroutines are separate programs, they proceed while exchanging messages. Do you see that the yield that appears in the generator is in charge of exchanging messages?

Write a coroutine with a generator

Now how do you use the generator like a coroutine? Write an example in JavaScript.

//Coroutine b
function* coroutine_b() { 
    const from_a1 = yield  //Receive a message from a
    yield "to_b"   //Return message to a
    const from_a2 = yield  //Wait for message from b
}

//Coroutine a
function* coroutine_a() { 
    val co_b = generator()
    co_b.next(message) //Send a message to b
    co_b.next() //Receive message from b
    co_b.next(message) //Send a message to b
}

Yield can be received in addition to returning the message.

Write animation state transitions in coroutines

Let's describe the state transition of the animation at the top of the article with a coroutine. Write in JavaScript.

function* generator() { 
    while(True) {
        const start_message = yield
        start(start_message)

        const frame1_message = yield
        if(!frame1(frame1_message)) {
            continue
        }
        
        const frame1_message = yield
        const from_a2 yield
    }
}

val co = generator()

val message = pullMessage();
co.next(message);

Can you see that the state transitions can be written in much the same way as a procedural program? I think that if and while statements can represent state transitions as they are, which simplifies validation and makes it easier to follow state transitions! You can use the concept of scope when exchanging variables between states, and you can easily exchange them because you can not use it where you do not need it.

Coroutine program division method

I think that the above technique is sufficient for a simple program, but for applications with fine state transitions such as games, you will want to divide coroutines and manage sub-states in the state.

If you use yield * in JavaScript and yield from in Python, the coroutine received from the method will be sent to you as if it were your own coroutine, so use this. Here is an example in JavaScript.


function* mode1() {
     input = yield  ;
     yield mode1_logic(input);
}

function* mode2() {
     input = yield  ;
     yield mode2_logic(input);
}

//Where to collect coroutines
function* comain() {
    yield* mode1();
    yield* mode2() ;
}

It's almost the same feeling as when returns are chained by procedural programming.

async await and coroutine together

I think that modern JavaScript makes heavy use of asynchronous processing using async/await, To use it in a coroutine, write as follows.

async function* generator() {
    val a = await fetch_data();  //Asynchronous data acquisition process
    yield a;
}

Summary

I wonder if there is something that can be described quite easily by using the generator as the original coroutine. I especially use it when writing communication programs such as Web Workers, WebSockets, and HTTP servers.

References

2 What was a "coroutine"? https://www.lambdanote.com/collections/n/products/nmonthly-vol-1-no-1-2019-ebook

Recommended Posts

Animation and communication state transitions can be easily described using JS and Python generators.
Non-linear simultaneous equations can be easily solved in Python.
Python iterators and generators
WiringPi-SPI communication using Python
Serial communication control with python and I2C communication (using USBGPIO8 device)
Scripts that can be used when using bottle in Python
Serial communication control with python and SPI communication (using USBGPIO8 device)