Skip to content
Permalink
Browse files

Initial coroutine/task system implementation

  • Loading branch information
Akaricchi committed Jun 26, 2019
1 parent 6ba4d4f commit bdef62d7f4ff6a3114f2f6e23b357985453f5730
Showing with 334 additions and 0 deletions.
  1. +3 −0 .gitmodules
  2. +1 −0 external/koishi
  3. +2 −0 meson.build
  4. +124 −0 src/coroutine.c
  5. +67 −0 src/coroutine.h
  6. +4 −0 src/main.c
  7. +1 −0 src/meson.build
  8. +2 −0 src/refs.h
  9. +4 −0 src/stage.c
  10. +105 −0 src/stages/corotest.c
  11. +18 −0 src/stages/corotest.h
  12. +2 −0 src/stages/meson.build
  13. +1 −0 subprojects/koishi
@@ -6,3 +6,6 @@
path = external/gamecontrollerdb
url = https://github.com/taisei-project/SDL_GameControllerDB.git
branch = master
[submodule "external/koishi"]
path = external/koishi
url = https://github.com/taisei-project/koishi
Submodule koishi added at cc6641
@@ -165,10 +165,12 @@ dep_m = cc.find_library('m', required : false)

dep_cglm = subproject('cglm').get_variable('cglm_dep')
dep_glad = subproject('glad').get_variable('glad_dep')
dep_koishi = subproject('koishi').get_variable('koishi_dep')

taisei_deps = [
dep_cglm,
dep_freetype,
dep_koishi,
dep_m,
dep_png,
dep_sdl2,
@@ -0,0 +1,124 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/

#include "taisei.h"

#include "coroutine.h"
#include "util.h"

#define CO_STACK_SIZE (64 * 1024)

struct CoTask {
LIST_INTERFACE(CoTask);
koishi_coroutine_t ko;
};

struct CoSched {
LIST_ANCHOR(CoTask) tasks, pending_tasks;
};

static LIST_ANCHOR(CoTask) task_pool;
static size_t task_pool_size;

CoSched *_cosched_global;

CoTask *cotask_new(CoTaskFunc func) {
CoTask *task;

if((task = alist_pop(&task_pool))) {
koishi_recycle(&task->ko, func);
log_debug("Recycled task %p for proc %p", (void*)task, *(void**)&func);
} else {
task = calloc(1, sizeof(*task));
koishi_init(&task->ko, CO_STACK_SIZE, func);
++task_pool_size;
log_debug("Created new task %p for proc %p (%zu tasks in pool)", (void*)task, *(void**)&func, task_pool_size);
}

return task;
}

void cotask_free(CoTask *task) {
alist_push(&task_pool, task);
}

void *cotask_resume(CoTask *task, void *arg) {
return koishi_resume(&task->ko, arg);
}

void *cotask_yield(void *arg) {
return koishi_yield(arg);
}

CoStatus cotask_status(CoTask *task) {
return koishi_state(&task->ko);
}

CoSched *cosched_new(void) {
CoSched *sched = calloc(1, sizeof(*sched));
return sched;
}

void *cosched_new_task(CoSched *sched, CoTaskFunc func, void *arg) {
CoTask *task = cotask_new(func);
void *ret = cotask_resume(task, arg);

if(cotask_status(task) == CO_STATUS_DEAD) {
cotask_free(task);
} else {
assert(cotask_status(task) == CO_STATUS_SUSPENDED);
alist_append(&sched->pending_tasks, task);
}

return ret;
}

uint cosched_run_tasks(CoSched *sched) {
for(CoTask *t; (t = alist_pop(&sched->pending_tasks));) {
alist_append(&sched->tasks, t);
}

uint ran = 0;

for(CoTask *t = sched->tasks.first, *next; t; t = next) {
next = t->next;
assert(cotask_status(t) == CO_STATUS_SUSPENDED);
cotask_resume(t, NULL);
++ran;

if(cotask_status(t) == CO_STATUS_DEAD) {
alist_unlink(&sched->tasks, t);
cotask_free(t);
}
}

return ran;
}

void cosched_free(CoSched *sched) {
for(CoTask *t = sched->pending_tasks.first, *next; t; t = next) {
next = t->next;
cotask_free(t);
}

for(CoTask *t = sched->tasks.first, *next; t; t = next) {
next = t->next;
cotask_free(t);
}
}

void coroutines_init(void) {

}

void coroutines_shutdown(void) {
for(CoTask *task; (task = alist_pop(&task_pool));) {
koishi_deinit(&task->ko);
free(task);
}
}
@@ -0,0 +1,67 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/

#ifndef IGUARD_coroutine_h
#define IGUARD_coroutine_h

#include "taisei.h"

#include <koishi.h>

typedef struct CoTask CoTask;
typedef struct CoSched CoSched;
typedef void *(*CoTaskFunc)(void *arg);

// target for the INVOKE_TASK macro
extern CoSched *_cosched_global;

typedef enum CoStatus {
CO_STATUS_SUSPENDED = KOISHI_SUSPENDED,
CO_STATUS_RUNNING = KOISHI_RUNNING,
CO_STATUS_DEAD = KOISHI_DEAD,
} CoStatus;

void coroutines_init(void);
void coroutines_shutdown(void);

CoTask *cotask_new(CoTaskFunc func);
void cotask_free(CoTask *task);
void *cotask_resume(CoTask *task, void *arg);
void *cotask_yield(void *arg);
CoStatus cotask_status(CoTask *task);

CoSched *cosched_new(void);
void *cosched_new_task(CoSched *sched, CoTaskFunc func, void *arg); // creates and runs the task, returns whatever it yields, schedules it for resume on cosched_run_tasks if it's still alive
uint cosched_run_tasks(CoSched *sched); // returns number of tasks ran
void cosched_free(CoSched *sched);

static inline attr_must_inline void cosched_set_invoke_target(CoSched *sched) { _cosched_global = sched; }

#define TASK(name, argstruct) \
typedef struct argstruct COARGS_##name; \
static void COTASK_##name(COARGS_##name ARGS); \
static void *COTASKTHUNK_##name(void *arg) { \
COTASK_##name(*(COARGS_##name*)arg); \
return NULL; \
} \
static void COTASK_##name(COARGS_##name ARGS)

#define INVOKE_TASK(name, ...) do { \
COARGS_##name _coargs = { __VA_ARGS__ }; \
cosched_new_task(_cosched_global, COTASKTHUNK_##name, &_coargs); \
} while(0)

#define YIELD cotask_yield(NULL)
#define WAIT(delay) do { int _delay = (delay); while(_delay-- > 0) YIELD; } while(0)

// to use these inside a coroutine, define a BREAK_CONDITION macro and a BREAK label.
#define CHECK_BREAK do { if(BREAK_CONDITION) goto BREAK; } while(0)
#define BYIELD do { YIELD; CHECK_BREAK; } while(0)
#define BWAIT(frames) do { WAIT(frames); CHECK_BREAK; } while(0)

#endif // IGUARD_coroutine_h
@@ -25,6 +25,7 @@
#include "version.h"
#include "credits.h"
#include "taskmanager.h"
#include "coroutine.h"

attr_unused
static void taisei_shutdown(void) {
@@ -49,6 +50,7 @@ static void taisei_shutdown(void) {
vfs_shutdown();
events_shutdown();
time_shutdown();
coroutines_shutdown();

log_info("Good bye");
SDL_Quit();
@@ -250,6 +252,8 @@ int main(int argc, char **argv) {

log_info("Girls are now preparing, please wait warmly...");

coroutines_init();

free_cli_action(&ctx->cli);
vfs_setup(CALLCHAIN(main_post_vfsinit, ctx));
return 0;
@@ -67,6 +67,7 @@ taisei_src = files(
'color.c',
'color.c',
'config.c',
'coroutine.c',
'credits.c',
'dialog.c',
'difficulty.c',
@@ -33,4 +33,6 @@ void del_ref(void *ptr);
void free_ref(int i);
void free_all_refs(void);

#define UPDATE_REF(ref, ptr) ((ptr) = REF(ref))

#endif // IGUARD_refs_h
@@ -95,6 +95,8 @@ static bool spellfilter_extra(AttackInfo *spell) {
return spell->type == AT_ExtraSpell;
}

#include "stages/corotest.h"

void stage_init_array(void) {
int spellnum = 0;

@@ -120,6 +122,8 @@ void stage_init_array(void) {
add_spellpractice_stage(stages, &stage1_spell_benchmark, &spellnum, STAGE_SPELL_BIT, D_Extra);
#endif

add_stage(0xC0, &corotest_procs, STAGE_SPECIAL, "Coroutines!", "wow such concurrency very async", NULL, D_Any);

end_stages();

#ifdef DEBUG
@@ -0,0 +1,105 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/

#include "taisei.h"

#include "corotest.h"
#include "coroutine.h"
#include "global.h"

static CoSched *cotest_sched;

static void cotest_stub_proc(void) { }

static int dummy_rule(Enemy *e, int t) { return t < 0 ? ACTION_ACK : ACTION_NONE; }

TASK(test_enemy, { double hp; complex pos; complex dir; }) {
Enemy *e = create_enemy1c(ARGS.pos, ARGS.hp, BigFairy, dummy_rule, 0);
int eref = add_ref(e);

#define BREAK_CONDITION !UPDATE_REF(eref, e)

BYIELD;

while(true) {
// wander around for a bit...
for(int i = 0; i < 20; ++i) {
e->pos += ARGS.dir;
BYIELD;
}

// stop and...
BWAIT(10);

// pew pew!!!
int pcount = 10 + 10 * frand();
for(int i = 0; i < pcount; ++i) {
complex aim = global.plr.pos - e->pos;
aim *= 5 * cexp(I*M_PI*0.1*nfrand()) / cabs(aim);

PROJECTILE(
.pos = e->pos,
.proto = pp_rice,
.color = RGBA(1.0, 0.0, i / (pcount - 1.0), 0.0),
.rule = linear,
.args = { aim },
);

BWAIT(3);
}

// keep wandering, randomly
ARGS.dir *= cexp(I*M_PI*nfrand());
BYIELD;
}

BREAK:
log_debug("enemy died!");
free_ref(eref);
}

TASK(stage_main, { int ignored; }) {
YIELD;

WAIT(30);
log_debug("test 1! %i", global.timer);
WAIT(60);
log_debug("test 2! %i", global.timer);

for(;;) {
INVOKE_TASK(test_enemy, 9000, CMPLX(VIEWPORT_W, VIEWPORT_H) * 0.5, 3*I);
WAIT(240);
}
}

static void cotest_begin(void) {
cotest_sched = cosched_new();
cosched_set_invoke_target(cotest_sched);
INVOKE_TASK(stage_main, 0);
}

static void cotest_end(void) {
cosched_free(cotest_sched);
}

static void cotest_events(void) {
if(!global.gameover && !cosched_run_tasks(cotest_sched)) {
log_debug("over!");
stage_finish(GAMEOVER_SCORESCREEN);
}
}

StageProcs corotest_procs = {
.begin = cotest_begin,
.preload = cotest_stub_proc,
.end = cotest_end,
.draw = cotest_stub_proc,
.update = cotest_stub_proc,
.event = cotest_events,
.shader_rules = (ShaderRule[]) { NULL },
};
@@ -0,0 +1,18 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/

#ifndef IGUARD_stages_corotest_h
#define IGUARD_stages_corotest_h

#include "taisei.h"

#include "stage.h"

extern StageProcs corotest_procs;

#endif // IGUARD_stages_corotest_h

0 comments on commit bdef62d

Please sign in to comment.
You can’t perform that action at this time.