254 lines
6.5 KiB
C++
254 lines
6.5 KiB
C++
/*
|
|
* Mr. 4th Dimention - Allen Webster
|
|
*
|
|
* 19.07.2017
|
|
*
|
|
* Coroutine implementation from thread+mutex+cv
|
|
*
|
|
*/
|
|
|
|
// TOP
|
|
|
|
typedef u32 Coroutine_State;
|
|
enum{
|
|
CoroutineState_Dead,
|
|
CoroutineState_Active,
|
|
CoroutineState_Inactive,
|
|
CoroutineState_Waiting,
|
|
};
|
|
|
|
typedef u32 Coroutine_Type;
|
|
enum{
|
|
CoroutineType_Uninitialized,
|
|
CoroutineType_Root,
|
|
CoroutineType_Sub,
|
|
};
|
|
|
|
struct Coroutine{
|
|
Coroutine_Head head;
|
|
|
|
Thread thread;
|
|
Condition_Variable cv;
|
|
struct Coroutine_System *sys;
|
|
Coroutine_Function *function;
|
|
Coroutine *yield_ctx;
|
|
Coroutine_State state;
|
|
Coroutine_Type type;
|
|
};
|
|
|
|
struct Coroutine_System{
|
|
Mutex lock;
|
|
Condition_Variable init_cv;
|
|
b32 did_init;
|
|
Coroutine *active;
|
|
};
|
|
|
|
#define COROUTINE_INT_SKIP_SLEEP false
|
|
|
|
internal void
|
|
coroutine_internal_pass_control(Coroutine *me, Coroutine *other, Coroutine_State my_new_state, b32 do_sleep_loop = true){
|
|
Assert(me->state == CoroutineState_Active);
|
|
Assert(me->sys == other->sys);
|
|
|
|
me->state = my_new_state;
|
|
other->state = CoroutineState_Active;
|
|
me->sys->active = other;
|
|
system_signal_cv(&other->cv, &me->sys->lock);
|
|
if (do_sleep_loop){
|
|
for (;me->state != CoroutineState_Active;){
|
|
system_wait_cv(&me->cv, &me->sys->lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal
|
|
PLAT_THREAD_SIG(coroutine_main){
|
|
Coroutine *me = (Coroutine*)ptr;
|
|
|
|
// NOTE(allen): Init handshake
|
|
Assert(me->state == CoroutineState_Dead);
|
|
system_acquire_lock(&me->sys->lock);
|
|
me->sys->did_init = true;
|
|
system_signal_cv(&me->sys->init_cv, &me->sys->lock);
|
|
|
|
for (;;){
|
|
// NOTE(allen): Wait until someone wakes us up, then go into our procedure.
|
|
for (;me->state != CoroutineState_Active;){
|
|
system_wait_cv(&me->cv, &me->sys->lock);
|
|
}
|
|
Assert(me->type != CoroutineType_Root);
|
|
Assert(me->yield_ctx != 0);
|
|
Assert(me->function != 0);
|
|
|
|
me->function(&me->head);
|
|
|
|
// NOTE(allen): Wake up the caller and set this coroutine back to being dead.
|
|
Coroutine *other = me->yield_ctx;
|
|
Assert(other != 0);
|
|
Assert(other->state == CoroutineState_Waiting);
|
|
|
|
coroutine_internal_pass_control(me, other, CoroutineState_Dead, COROUTINE_INT_SKIP_SLEEP);
|
|
me->function = 0;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
init_coroutine_system(Coroutine *root, Coroutine_System *sys){
|
|
system_init_lock(&sys->lock);
|
|
system_init_cv(&sys->init_cv);
|
|
sys->active = root;
|
|
|
|
memset(root, 0, sizeof(*root));
|
|
|
|
root->sys = sys;
|
|
root->state = CoroutineState_Active;
|
|
root->type = CoroutineType_Root;
|
|
|
|
system_init_cv(&root->cv);
|
|
}
|
|
|
|
internal void
|
|
init_coroutine_sub(Coroutine *co, Coroutine_System *sys){
|
|
memset(co, 0, sizeof(*co));
|
|
|
|
co->sys = sys;
|
|
co->state = CoroutineState_Dead;
|
|
co->type = CoroutineType_Sub;
|
|
|
|
system_init_cv(&co->cv);
|
|
|
|
sys->did_init = false;
|
|
system_init_and_launch_thread(&co->thread, coroutine_main, co);
|
|
for (;!sys->did_init;){
|
|
system_wait_cv(&sys->init_cv, &sys->lock);
|
|
}
|
|
}
|
|
|
|
// HACK(allen): I want to bundle this with launch!
|
|
internal void
|
|
coroutine_set_function(Coroutine *me, Coroutine_Function *function){
|
|
Assert(me->state == CoroutineState_Dead);
|
|
me->function = function;
|
|
}
|
|
|
|
internal void
|
|
coroutine_launch(Coroutine *me, Coroutine *other){
|
|
Assert(me->sys == other->sys);
|
|
Assert(other->state == CoroutineState_Dead);
|
|
Assert(other->function != 0);
|
|
|
|
other->yield_ctx = me;
|
|
coroutine_internal_pass_control(me, other, CoroutineState_Waiting);
|
|
}
|
|
|
|
internal void
|
|
coroutine_yield(Coroutine *me){
|
|
Coroutine *other = me->yield_ctx;
|
|
Assert(other != 0);
|
|
Assert(me->sys == other->sys);
|
|
Assert(other->state == CoroutineState_Waiting);
|
|
|
|
coroutine_internal_pass_control(me, other, CoroutineState_Inactive);
|
|
}
|
|
|
|
internal void
|
|
coroutine_resume(Coroutine *me, Coroutine *other){
|
|
Assert(me->sys == other->sys);
|
|
Assert(other->state == CoroutineState_Inactive);
|
|
|
|
other->yield_ctx = me;
|
|
coroutine_internal_pass_control(me, other, CoroutineState_Waiting);
|
|
}
|
|
|
|
////////////////////////////////
|
|
|
|
struct Coroutine_Alloc_Block{
|
|
Coroutine coroutine;
|
|
Coroutine_Alloc_Block *next;
|
|
};
|
|
|
|
#define COROUTINE_SLOT_SIZE sizeof(Coroutine_Alloc_Block)
|
|
|
|
struct Coroutine_System_Auto_Alloc{
|
|
Coroutine_System sys;
|
|
|
|
Coroutine root;
|
|
Coroutine_Alloc_Block *head_free_uninit;
|
|
Coroutine_Alloc_Block *head_free_inited;
|
|
};
|
|
|
|
internal void
|
|
init_coroutine_system(Coroutine_System_Auto_Alloc *sys){
|
|
init_coroutine_system(&sys->root, &sys->sys);
|
|
sys->head_free_uninit = 0;
|
|
sys->head_free_inited = 0;
|
|
}
|
|
|
|
internal void
|
|
coroutine_system_provide_memory(Coroutine_System_Auto_Alloc *sys, void *memory, umem size){
|
|
memset(memory, 0, size);
|
|
|
|
Coroutine_Alloc_Block *blocks = (Coroutine_Alloc_Block*)memory;
|
|
umem count = (size / sizeof(*blocks));
|
|
|
|
Coroutine_Alloc_Block *old_head = sys->head_free_uninit;
|
|
sys->head_free_uninit = &blocks[0];
|
|
Coroutine_Alloc_Block *block = blocks;
|
|
for (u32 i = 1; i < count; ++i, ++block){
|
|
block->next = block + 1;
|
|
}
|
|
block->next = old_head;
|
|
}
|
|
|
|
internal Coroutine_Alloc_Block*
|
|
coroutine_system_pop(Coroutine_Alloc_Block **head){
|
|
Coroutine_Alloc_Block *result = *head;
|
|
if (result != 0){
|
|
*head = result->next;
|
|
}
|
|
result->next = 0;
|
|
return(result);
|
|
}
|
|
|
|
internal void
|
|
coroutine_system_push(Coroutine_Alloc_Block **head, Coroutine_Alloc_Block *block){
|
|
block->next = *head;
|
|
*head = block;
|
|
}
|
|
|
|
internal void
|
|
coroutine_system_force_init(Coroutine_System_Auto_Alloc *sys, u32 count){
|
|
for (u32 i = 0; i < count; ++i){
|
|
Coroutine_Alloc_Block *block = coroutine_system_pop(&sys->head_free_uninit);
|
|
if (block == 0){
|
|
break;
|
|
}
|
|
init_coroutine_sub(&block->coroutine, &sys->sys);
|
|
coroutine_system_push(&sys->head_free_inited, block);
|
|
}
|
|
}
|
|
|
|
internal Coroutine*
|
|
coroutine_system_alloc(Coroutine_System_Auto_Alloc *sys){
|
|
Coroutine_Alloc_Block *block = coroutine_system_pop(&sys->head_free_inited);
|
|
if (block == 0){
|
|
coroutine_system_force_init(sys, 1);
|
|
block = coroutine_system_pop(&sys->head_free_inited);
|
|
}
|
|
|
|
Coroutine *result = 0;
|
|
if (block != 0){
|
|
result = &block->coroutine;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
internal void
|
|
coroutine_system_free(Coroutine_System_Auto_Alloc *sys, Coroutine *co){
|
|
Coroutine_Alloc_Block *block = (Coroutine_Alloc_Block*)co;
|
|
coroutine_system_push(&sys->head_free_inited, block);
|
|
}
|
|
|
|
// BOTTOM
|
|
|