165 lines
4.6 KiB
C++
165 lines
4.6 KiB
C++
|
/*
|
||
|
* Mr. 4th Dimention - Allen Webster
|
||
|
*
|
||
|
* 19.07.2017
|
||
|
*
|
||
|
* Coroutine implementation from thread+mutex+cv
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
// TOP
|
||
|
|
||
|
internal void
|
||
|
coroutine__pass_control(Coroutine *me, Coroutine *other,
|
||
|
Coroutine_State my_new_state, Coroutine_Pass_Control control){
|
||
|
Assert(me->state == CoroutineState_Active);
|
||
|
Assert(me->sys == other->sys);
|
||
|
|
||
|
me->state = my_new_state;
|
||
|
other->state = CoroutineState_Active;
|
||
|
me->sys->active = other;
|
||
|
system_condition_variable_signal(other->cv);
|
||
|
if (control == CoroutinePassControl_BlockMe){
|
||
|
for (;me->state != CoroutineState_Active;){
|
||
|
system_condition_variable_wait(me->cv, me->sys->lock);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
coroutine_main(void *ptr){
|
||
|
Coroutine *me = (Coroutine*)ptr;
|
||
|
|
||
|
Thread_Context_Extra_Info tctx_info = {};
|
||
|
tctx_info.coroutine = me;
|
||
|
|
||
|
Thread_Context tctx_ = {};
|
||
|
thread_ctx_init(&tctx_, ThreadKind_MainCoroutine,
|
||
|
get_base_allocator_system(), get_base_allocator_system());
|
||
|
tctx_.user_data = &tctx_info;
|
||
|
me->tctx = &tctx_;
|
||
|
|
||
|
// NOTE(allen): Init handshake
|
||
|
Assert(me->state == CoroutineState_Dead);
|
||
|
system_mutex_acquire(me->sys->lock);
|
||
|
me->sys->did_init = true;
|
||
|
system_condition_variable_signal(me->sys->init_cv);
|
||
|
|
||
|
for (;;){
|
||
|
// NOTE(allen): Wait until someone wakes us up, then go into our procedure.
|
||
|
for (;me->state != CoroutineState_Active;){
|
||
|
system_condition_variable_wait(me->cv, me->sys->lock);
|
||
|
}
|
||
|
Assert(me->type != CoroutineType_Root);
|
||
|
Assert(me->yield_ctx != 0);
|
||
|
Assert(me->func != 0);
|
||
|
|
||
|
me->func(me);
|
||
|
|
||
|
// 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__pass_control(me, other, CoroutineState_Dead, CoroutinePassControl_ExitMe);
|
||
|
me->func = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
coroutine_sub_init(Coroutine *co, Coroutine_Group *sys){
|
||
|
block_zero_struct(co);
|
||
|
co->sys = sys;
|
||
|
co->state = CoroutineState_Dead;
|
||
|
co->type = CoroutineType_Sub;
|
||
|
co->cv = system_condition_variable_make();
|
||
|
sys->did_init = false;
|
||
|
co->thread = system_thread_launch(coroutine_main, co);
|
||
|
for (;!sys->did_init;){
|
||
|
system_condition_variable_wait(sys->init_cv, sys->lock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
coroutine_system_init(Coroutine_Group *sys){
|
||
|
sys->arena = make_arena_system();
|
||
|
|
||
|
Coroutine *root = &sys->root;
|
||
|
|
||
|
sys->lock = system_mutex_make();
|
||
|
sys->init_cv = system_condition_variable_make();
|
||
|
sys->active = root;
|
||
|
|
||
|
block_zero_struct(root);
|
||
|
root->sys = sys;
|
||
|
root->state = CoroutineState_Active;
|
||
|
root->type = CoroutineType_Root;
|
||
|
root->cv = system_condition_variable_make();
|
||
|
|
||
|
sys->unused = 0;
|
||
|
|
||
|
system_mutex_acquire(sys->lock);
|
||
|
}
|
||
|
|
||
|
internal Coroutine*
|
||
|
coroutine_system_alloc(Coroutine_Group *sys){
|
||
|
Coroutine *result = sys->unused;
|
||
|
if (result != 0){
|
||
|
sll_stack_pop(sys->unused);
|
||
|
}
|
||
|
else{
|
||
|
result = push_array(&sys->arena, Coroutine, 1);
|
||
|
coroutine_sub_init(result, sys);
|
||
|
}
|
||
|
result->next = 0;
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
coroutine_system_free(Coroutine_Group *sys, Coroutine *coroutine){
|
||
|
sll_stack_push(sys->unused, coroutine);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
|
||
|
internal Coroutine*
|
||
|
coroutine_create(Coroutine_Group *coroutines, Coroutine_Function *func){
|
||
|
Coroutine *result = coroutine_system_alloc(coroutines);
|
||
|
Assert(result->state == CoroutineState_Dead);
|
||
|
result->func = func;
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal Coroutine*
|
||
|
coroutine_run(Coroutine_Group *sys, Coroutine *other, void *in, void *out){
|
||
|
other->in = in;
|
||
|
other->out = out;
|
||
|
|
||
|
Coroutine *me = other->sys->active;
|
||
|
Assert(me != 0);
|
||
|
Assert(me->sys == other->sys);
|
||
|
Assert(other->state == CoroutineState_Dead || other->state == CoroutineState_Inactive);
|
||
|
other->yield_ctx = me;
|
||
|
coroutine__pass_control(me, other, CoroutineState_Waiting, CoroutinePassControl_BlockMe);
|
||
|
Assert(me == other->sys->active);
|
||
|
|
||
|
Coroutine *result = other;
|
||
|
if (other->state == CoroutineState_Dead){
|
||
|
coroutine_system_free(sys, other);
|
||
|
result = 0;
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
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__pass_control(me, other, CoroutineState_Inactive, CoroutinePassControl_BlockMe);
|
||
|
}
|
||
|
|
||
|
// BOTTOM
|
||
|
|