Sol2: Multiple coroutine sequential execution

Created on 8 Nov 2020  路  4Comments  路  Source: ThePhD/sol2

Hello,
I'm currently playing with coroutine with Lua and Sol and I'm actually a bit confused.
Indeed, I'm trying to reproduce something like that:

Lua Code:

co1 = coroutine.create(
    function()
            print("AA : Step 1")
            coroutine.yield()
            print("AA : Step 2")
    end
)
coroutine.resume(co1)

co2 = coroutine.create(
    function()
            print("BB : Step 1")
            coroutine.yield()
            print("BB : Step 2")
    end
)
coroutine.resume(co2)

coroutine.resume(co1)

Output:

$ lua test.lua
AA : Step 1
BB : Step 1
AA : Step 2

So I wanted to be able to do the same thing using sol::coroutine, but it seems that the wrong coroutine is executed:

#include <sol/sol.hpp>
#include <iostream>

int main()
{
    sol::state lua;
    lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::coroutine);

    lua["print"] = [](const sol::lua_value &v){
        std::cout << v.as<std::string>() << std::endl;
    };
    lua["cyield"] = sol::yielding([](){
        std::cout << "YIELDING" << std::endl;
    });

    sol::coroutine co1 = lua.load(R"(
        print("AA : Step 1")
        cyield()
        print("AA : Step 2")
    )");
    co1();

    sol::coroutine co2 = lua.load(R"(
        print("BB : Step 1")
        cyield()
        print("BB : Step 2")
    )");
    co2();

    co1();

    return 0;
}

Output :

AA : Step 1
YIELDING
AA : Step 2
AA : Step 1
YIELDING

I read some things about using sol::thread but I don't understand the way I should use it, for me I don't want to multithread my code, so I don't know if I'm on the good way.

Thanks for your help !

Feature.Can Do Feature.Shiny Help.Desk

Most helpful comment

Current name idea so far is sol::packed_coroutine.

This is also going to make me create a sol3 header. where I create aliases under a sol3:: namespace using _good_ names for everything....

All 4 comments

So, the reason you need sol::thread is because each of those coroutines needs its _own execution stack_. I call sol::thread by that name because that's how the Lua API and documentation refers to such an entity: a "thread".

coroutine.create(...) is hella wrong by Lua here, because coroutine.create doesn't return you a coroutine: it returns you a _thread_ (an execution stack) with an associated coroutine.

In reality, it's closer to sol::execution_stack. It is a context in which the coroutine runs, and each coroutine needs its own sol::thread to have independent variables that don't clash with one another and its own return types. That's why the examples (https://sol2.readthedocs.io/en/latest/api/coroutine.html#yield-main-thread) go through some shenanigans, to give you the right code:

#include <sol/sol.hpp>
#include <iostream>

int main()
{
    sol::state lua;
    lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::coroutine);

    lua["print"] = [](sol::object v){
        std::cout << v.as<std::string>() << std::endl;
    };
    lua["cyield"] = sol::yielding([](){
        std::cout << "YIELDING" << std::endl;
    });

    // notice the new threads!
    sol::thread thread1 = sol::thread::create(lua);
    sol::thread thread2 = sol::thread::create(lua);

    // notice we load it FROM the new "execution stack"
    // we need it to have thread1's stack perspective
    sol::coroutine co1 = thread1.state().load(R"(
        print("AA : Step 1")
        cyield()
        print("AA : Step 2")
    )");
    // call first coroutine here
    co1();

    // notice we load it FROM the new "execution stack"
    // we need it to have thread2's stack and perspective
    sol::coroutine co2 = thread2.state().load(R"(
        print("BB : Step 1")
        cyield()
        print("BB : Step 2")
    )");

    // run the other coroutine
    co2();
    co1();
    // tada! they run on
    // independent stacks

    return 0;
}

Note to self

Given the number of times this has happened, I'm beginning to think I should write a sol::bundled_coroutine and completely change the docs to have people use that, where it automatically creates a new stack to be paired with every coroutine. The current interface is one-to-one with low-level concepts, and honestly that's causing people more heartache than benefit right now...

(To be clear, there is no sol::execution_stack. It doesn't exist; it's just a better name than sol::thread. Still, I need a good name for something like coroutine_but_it_has_the_thread_too...)

Thanks a lot for your reply, indeed this naming and the context itself is a bit confusing, maybe something named like coroutine_context could make it ?

Current name idea so far is sol::packed_coroutine.

This is also going to make me create a sol3 header. where I create aliases under a sol3:: namespace using _good_ names for everything....

Was this page helpful?
0 / 5 - 0 ratings

Related issues

RyanSquared picture RyanSquared  路  4Comments

Rochet2 picture Rochet2  路  7Comments

mrgreywater picture mrgreywater  路  4Comments

squeek502 picture squeek502  路  3Comments

morsisko picture morsisko  路  3Comments