Skip to content

Instantly share code, notes, and snippets.

@RandyGaul
Created October 20, 2024 02:21
Show Gist options
  • Save RandyGaul/8522207951f44feb8f801fab399df092 to your computer and use it in GitHub Desktop.
Save RandyGaul/8522207951f44feb8f801fab399df092 to your computer and use it in GitHub Desktop.
C Coroutine (macro based)
// A portable "coroutine"-like thing for implementing FSM and behavior-cycles.
//
// Original implementation by Noel Berry.
// See: https://gist.github.com/NoelFB/7a5fa66fc29dd7ed1c11042c30f1b00e
//
// Routine is a set of macros useful to implement Finite State Machine type of
// behaviors. It works really well for simpler behaviors with loops or cycles,
// and works especially well for anything resembling a cutscene.
//
// The macros here are wrappers around a switch statement. All of the `rt_***`
// macros save a bookmark. On the next frame the routine is resumed at the last
// bookmark.
//
// Each `rt_***` macro has a block { }. It contains code to run, and is attached
// to the `rt_***` macro. The exception is `rt_end` has no block. Blocks can have
// local variables but they don't persist, so be careful with them. A block will
// run for one frame.
//
// Example:
//
// #define STATE_1 1
// #define STATE_2 2
//
// rt_begin(routine)
// {
// // This is the starting block. Runs once by default.
// }
//
// rt_state(STATE_1)
// {
// // Runs once.
// }
// rt_seconds(3)
// {
// // Runs once per frame for 3 seconds.
// }
// rt_wait(1)
// {
// // Waits 1 second, then runs this once.
// }
// rt_once()
// {
// // Runs this one time.
//
// if (key_press(CF_KEY_SPACE))
// nav_goto(STATE_2); // Next frame will begin on `rt_state(STATE_2)`.
// else
// nav_goto(STATE_1); // Restart this state.
// }
//
// rt_state(STATE_2) { } // Empty block.
// rt_wait(5.0f) { } // Empty block. This is like a long "cooldown" of 5 seconds.
// rt_once()
// {
// nav_restart(); // Restart the whole routine back at `rt_begin`.
// }
// rt_end();
typedef struct Routine
{
// Goes from 0 to 1 over X seconds during `rt_seconds`.
// Useful for animating things.
float elapsed;
// Used in `rt_seconds` to repeat the block for X seconds.
float seconds;
// Used in `rt_wait` to wait for X seconds.
float wait_elapsed;
// Bookmark for which block in the routine we are currently at.
uint64_t at;
// Used to facilitate the `nav_goto_now` macro.
bool goto_now;
} Routine;
// Begin the routine.
#define rt_begin(rt) \
do { \
Routine* __rt = rt; \
bool __mn = true; \
if (__rt->wait_elapsed > 0) __rt->wait_elapsed -= CF_DELTA_TIME; \
else { __rt_begin: switch (__rt->at) { \
case 0: do { \
// State that can be jumped to with `nav_goto(state)`.
#define rt_state(state) \
} while (0); if (__mn) __rt->at = state; break; \
case state: do { \
// Runs its block once.
#define rt_once() \
} while (0); if (__mn) __rt->at = __LINE__; break; \
case __LINE__: do { \
// Runs its block for `time` seconds.
#define rt_seconds(time) \
rt_once(); \
if (__rt->seconds < (time)) { \
__rt->seconds += CF_DELTA_TIME; \
__mn = __rt->seconds >= (time); \
if (__mn) { \
__rt->elapsed = 1; \
__rt->seconds = 0; \
} else { \
__rt->elapsed = __rt->seconds / (float)(time); \
} \
} \
// Runs its block while `condition` is true.
#define rt_while(condition) \
} while (0); if (__mn) __rt->at = __LINE__; break; \
case __LINE__: if (condition) \
do { __mn = false; \
// Runs the block indefinitely.
#define rt_always() \
rt_while(1)
// Runs its block once when `condition` becomes true.
#define rt_upon(condition) \
} while (0); if (__mn) { \
__rt->at = (condition) ? __LINE__ : -__LINE__; \
} break; \
case -__LINE__: \
if (condition) __rt->at = __LINE__; \
break; \
case __LINE__: do { \
// Waits for `time` seconds before running its block.
#define rt_wait(time) \
} while (0); if (__mn) { \
__rt->wait_elapsed = (time); \
__rt->at = __LINE__; \
} \
break; \
case __LINE__: do { \
#ifdef _MSC_VER
#pragma warning(disable:4102) // Unreferenced goto label (sometimes from rt_end).
#endif
// End of the routine. No block needed.
#define rt_end() \
} while (0); if (__mn) __rt->at = -1; \
break; \
} } __rt_end:; \
if (__rt->goto_now) { __rt->goto_now = false; goto __rt_begin; } \
} while (0) \
// -------------------------------------------------------------------------------------------------
// Navigation Macros
// Sets the next state for the routine.
#define nav_next(state) do { CF_MEMSET(__rt, 0, sizeof(*__rt)); __rt->at = state; } while (0)
// Repeats the block where this is placed.
#define nav_redo() \
do { goto __rt_end; } while (0)
// Goes to the block with the specified state on the next frame.
#define nav_goto(state) \
do { \
__rt->at = state; \
goto __rt_end; \
} while (0)
// Goes to the block with the specified state without frame delay.
#define nav_goto_now(state) \
do { \
__rt->at = state; \
goto __rt_begin; \
} while (0)
// Restarts the whole routine.
#define nav_restart() \
do { \
__rt->at = 0; \
__rt->elapsed = 0; \
__rt->seconds = 0; \
__rt->wait_elapsed = 0; \
goto __rt_end; \
} while (0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment