Created
October 20, 2024 02:21
-
-
Save RandyGaul/8522207951f44feb8f801fab399df092 to your computer and use it in GitHub Desktop.
C Coroutine (macro based)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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