so I'm noodling away on my hobby programming language 1, and I'm at the point where I'd like to write small game loop with it. the only problem is that I have no way currently to yield control from within the program to its environment 2. this makes a game loop pretty hard
one option is to write the game loop as a function and just have the host repeatedly call the function with the game state as a parameter. this sorta sucks because I don't want to marshal the whole game state across an FFI boundary every frame. another option is to stick the game state in a global and then repeatedly call a function that operates on the global state. however... I don't have globals support yet
the last option is to implement coroutines. this is clearly a big yak to shave, but I do want some form of coroutines in the language regardless. it should be a fun challenge because it's pretty different from everything else I've done so far (which is mostly basic data structures and control flow support). currently the syntax for a coroutine looks like this:
gen fn infinite_seq(): generator[i32, void] {
let current = 0;
while true {
yield current;
current += 1;
}
}
let seq = infinite_seq();
let value = seq() + seq() + seq();
print(value);
I'm not 100% sure about the generator type, but currently it takes two parameters: one that it produces (i32) and one that in consumes (in this case, void for no parameters required). I guess in the case where multiple values are consumed, you'd use a tuple? not entirely sure.
the interesting problem with coroutines is deciding between the approach Rust and Go take. as I understand it, Rust uses an internal-only coroutine implementation for async functions. it creates a state machine with a size known at compile time, so instances of coroutines or tasks can be passed around as normal values. Go instead does a bunch of dynamic stuff with the stack, which is why doing Go FFI is painful and slow. I'd like the zero-overhead option Rust goes for, but I'm not sure the resulting complexity of implementation and of use is worth it for me. This project is very much shooting for "just good enough" performance at runtime, so a little overhead like Go's implementation should be fine?
all I've done so far is the syntax, I very much still need to add all the typechecking and all the compilation support for suspending / resuming functions. I'll probably post about it again when that's all done!
-
it badly needs a name; I've been calling it "Brick" but there's an existing PL project called Brick
-
currently the environment is a very simple interpreter in Rust, but a future wasm backend is very much planned
