handmade.network » Forums » Thoughts on Coroutines
MajorMalfuncion00
Jesse James Lactin
3 posts
#11695 Thoughts on Coroutines
1 month, 1 week ago

I've been toying around with setjmp/longjmp (SJLJ)-based coroutines for the last week or so, and I like them. I was wondering about Casey's thoughts on them. I found them to be quite a cool construct. I think it may also have a place in HH's game code. Here's a snippet of code from Yossi Krenin's blog. Could they be covered in an HH episode?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include "coroutine.h"

typedef struct {
  coroutine* c;
  int max_x, max_y;
  int x, y;
} iter;

void iterate(void* p) {
  iter* it = (iter*)p;
  int x,y;
  for(x=0; x<it->max_x; x++) {
    for(y=0; y<it->max_y; y++) {
      it->x = x;
      it->y = y;
      yield(it->c);
    }
  }
}

#define N 1024

int main() {
  coroutine c;
  int stack[N];
  iter it = {&c, 3, 2};
  start(&c, &iterate, &it, stack+N);
  while(next(&c)) {
    printf("x=%d y=%dn", it.x, it.y);
  }
}

Here's the link to the implementation of start/next/yield here: https://www.embeddedrelated.com/showarticle/455.php
ratchetfreak
262 posts
#11696 Thoughts on Coroutines
1 month, 1 week ago

coroutines are fun but it's a very obvious case of moving complexity elsewhere and with the cost of not knowing what is running at any one time.

I would prefer stackless coroutines because 90% of the time you never yield anywhere but the coroutine function anyway.

These can be implemented yourself as a state machine where every variable you need preserved between calls must be saved explicitly in the context. That reduces all your longjumps into basic function calls.
abnercoimbre
Abner Coimbre
189 posts
2 projects

Community Builder

#11697 Thoughts on Coroutines
1 month, 1 week ago Edited by Abner Coimbre on April 15, 2017, 11:15 a.m.

ratchetfreak:
coroutines are fun

Here's my preferred way to keep track of where you came from. What? Weren't we enjoying ourselves?

EDIT: I think we should encourage exploring constructs this way. To place on production code, however..

Programmer for a certain space agency
MajorMalfuncion00
Jesse James Lactin
3 posts
#11699 Thoughts on Coroutines
1 month, 1 week ago

abnercoimbre:
ratchetfreak:
coroutines are fun

Here's my preferred way to keep track of where you came from. What? Weren't we enjoying ourselves?

EDIT: I think we should encourage exploring constructs this way. To place on production code, however..


The only thing I don't like about Duff's Device-based coroutines in that they hijack switch statements. I'd say that having a full-blown scheduler with SJLJ coroutines is asking for trouble. These kind of coroutines particularly good for implementing stateful generators. If this was to be pursued, a fiber-based system is probably easier to debug and maintain.
pragmatic_hero
30 posts
#11705 Thoughts on Coroutines
1 month, 1 week ago Edited by on April 17, 2017, 12:11 a.m.

ratchetfreak:
coroutines are fun but it's a very obvious case of moving complexity elsewhere and with the cost of not knowing what is running at any one time.

What do you mean?

Correct me if i'm wrong, but the way coroutines are used is -
  • you have a list of N active coroutines
  • and a list of M coroutines which are suspended (and are going to get activated after X ms).
  • all coroutines are "updated" at once at a certain place in your main loop, so its easy to measure their runtime. And are comparable to N function calls + the overhead of setjmp, longjmp, and whatever else goes into storing/restoring the state of the stack

  • You can always add a debug string to each coroutine so you always know the names of the functions which are being "stepped through".

    Its like having N suspended function calls that run on your main thread.
    Where each yield statement is like adding a state in the state-machine without having to write code to branch into that state, or write the "struct" to hold all the state data - its all "described" in that single function, with state stored on stack.

    It is like cheating. It certainly does feel like cheating.

    Since it's running on a single thread there are no "surprising" side effects, unless you go into complete crazy town with them.

    I find them invaluable when doing gameplay programming.
    Super useful for cutscenes in RPG-like games, or scripted events of any sort, in pseudocode:
    1
    2
    3
    4
    5
    6
    7
    8
    disable_controls();
    walk_left(5 tiles);
    play_animation("victory")
    wait(2sec);
    say_text("bla bla bla bla bla bla");
    create_item(x,y, "heart");
    walk_right(5 tiles);
    enable_controls();
    

    There's so much more that can be done with them.
    (calling a coroutine from coroutine, spawning multiple coroutines in parallel and waiting for both of them to finish before continuing on, etc)
    Super powerful construct for games.

    If they can be implemented in C in a way which is not cumbersome to use, it's a definite win (I use them in C# for gameplay all the time)
    ratchetfreak
    262 posts
    #11706 Thoughts on Coroutines
    1 month, 1 week ago

    pragmatic_hero:

    Since it's running on a single thread there are no "surprising" side effects, unless you go into complete crazy town with them.


    Part of the problem is that people will overuse the shiny new hammer they've been handed.

    At it's core they are little statemachines and to debug them you need to view them as statemachines. That requires both a human readable string to identify the state machine and a way to know what state the machine is in in the debugger.
    MajorMalfuncion00
    Jesse James Lactin
    3 posts
    #11708 Thoughts on Coroutines
    1 month, 1 week ago

    pragmatic_hero:


    You can always add a debug string to each coroutine so you always know the names of the functions which are being "stepped through".

    Its like having N suspended function calls that run on your main thread.
    Where each yield statement is like adding a state in the state-machine without having to write code to branch into that state, or write the "struct" to hold all the state data - its all "described" in that single function, with state stored on stack.

    It is like cheating. It certainly does feel like cheating.

    Since it's running on a single thread there are no "surprising" side effects, unless you go into complete crazy town with them.

    If they can be implemented in C in a way which is not cumbersome to use, it's a definite win (I use them in C# for gameplay all the time)

    It's good to know that coroutines have a use-case in games. I thought this kind of code is easy to write and debug once you get used to inspecting a machine stack-based state machine. On the implementation, this code depends on setjmp, which is used to save/restore the $sp, $fp and $pc. Their initial change is made through inline assembly (sorry MSVC users). The alternative to inline assembly is to write start() in assembly. yield() and next() can be implemented each in a few lines of C. My addition was to pass arguments via varargs. It makes it look like the sample Python code at the link, but it can segfault if arguments are missing.
    stevetranby
    Steve Tranby
    11 posts

    Code Monkey

    #11709 Thoughts on Coroutines
    1 month, 1 week ago

    I'll only add that my experience with using coroutines in game logic scripts, written in C# (unity) or Lua (c++ engine), are simple enough that time spent debug has been negligible relative to everything else. It is likely this is because the script functions that include yield are simple enough, similar to the one pragmatic_hero posted.

    I also believe that it's a useful construct for a language/library to include even if you can shoot yourself in the foot.

    I see 0xdeadc0de everywhere