experimental event system for coroutine-based stages

This commit is contained in:
Andrei Alexeyev 2019-07-17 05:04:49 +03:00
parent 1579082e30
commit 9fe54e0335
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
7 changed files with 176 additions and 11 deletions

2
external/koishi vendored

@ -1 +1 @@
Subproject commit cc6641ee7a3b0c17df4bfa56e6367bdb2e3ffc94
Subproject commit 47e63b57018a62a300d797ea518578850c60730b

View file

@ -15,6 +15,7 @@
struct CoTask {
LIST_INTERFACE(CoTask);
List event_subscriber_chain;
koishi_coroutine_t ko;
BoxedEntity bound_ent;
};
@ -66,6 +67,30 @@ void *cotask_yield(void *arg) {
return koishi_yield(arg);
}
bool cotask_wait_event(CoEvent *evt, void *arg) {
struct {
uint32_t unique_id;
uint32_t num_signaled;
} snapshot = { evt->unique_id, evt->num_signaled };
alist_append(&evt->subscribers, &cotask_active()->event_subscriber_chain);
while(true) {
if(
evt->unique_id != snapshot.unique_id ||
evt->num_signaled < snapshot.num_signaled
) {
return false;
}
if(evt->num_signaled > snapshot.num_signaled) {
return true;
}
cotask_yield(arg);
}
}
CoStatus cotask_status(CoTask *task) {
return koishi_state(&task->ko);
}
@ -75,6 +100,33 @@ void cotask_bind_to_entity(CoTask *task, EntityInterface *ent) {
task->bound_ent = ent_box(ent);
}
void coevent_init(CoEvent *evt) {
static uint32_t g_uid;
evt->unique_id = ++g_uid;
evt->num_signaled = 0;
assert(g_uid != 0);
}
static void coevent_wake_subscribers(CoEvent *evt) {
for(List *node = evt->subscribers.first; node; node = node->next) {
CoTask *task = CASTPTR_ASSUME_ALIGNED((char*)node - offsetof(CoTask, event_subscriber_chain), CoTask);
cotask_resume(task, NULL);
}
evt->subscribers.first = evt->subscribers.last = NULL;
}
void coevent_signal(CoEvent *evt) {
++evt->num_signaled;
assert(evt->num_signaled != 0);
coevent_wake_subscribers(evt);
}
void coevent_cancel(CoEvent* evt) {
evt->num_signaled = evt->unique_id = 0;
coevent_wake_subscribers(evt);
}
CoSched *cosched_new(void) {
CoSched *sched = calloc(1, sizeof(*sched));
return sched;
@ -103,13 +155,14 @@ uint cosched_run_tasks(CoSched *sched) {
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);
} else {
assert(cotask_status(t) == CO_STATUS_SUSPENDED);
cotask_resume(t, NULL);
++ran;
}
}

View file

@ -27,6 +27,12 @@ typedef enum CoStatus {
CO_STATUS_DEAD = KOISHI_DEAD,
} CoStatus;
typedef struct CoEvent {
ListAnchor subscribers;
uint32_t unique_id;
uint32_t num_signaled;
} CoEvent;
void coroutines_init(void);
void coroutines_shutdown(void);
@ -34,10 +40,15 @@ CoTask *cotask_new(CoTaskFunc func);
void cotask_free(CoTask *task);
void *cotask_resume(CoTask *task, void *arg);
void *cotask_yield(void *arg);
bool cotask_wait_event(CoEvent *evt, void *arg);
CoStatus cotask_status(CoTask *task);
CoTask *cotask_active(void);
void cotask_bind_to_entity(CoTask *task, EntityInterface *ent);
void coevent_init(CoEvent *evt);
void coevent_signal(CoEvent *evt);
void coevent_cancel(CoEvent *evt);
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
@ -46,21 +57,38 @@ void cosched_free(CoSched *sched);
static inline attr_must_inline void cosched_set_invoke_target(CoSched *sched) { _cosched_global = sched; }
#define TASK(name, argstruct) \
static char COTASK_UNUSED_CHECK_##name; \
typedef struct argstruct COARGS_##name; \
static void COTASK_##name(COARGS_##name ARGS); \
static void *COTASKTHUNK_##name(void *arg) { \
attr_unused static void *COTASKTHUNK_##name(void *arg) { \
COTASK_##name(*(COARGS_##name*)arg); \
return NULL; \
} \
typedef struct { CoEvent *event; COARGS_##name args; } COARGSCOND_##name; \
attr_unused static void *COTASKTHUNKCOND_##name(void *arg) { \
COARGSCOND_##name args = *(COARGSCOND_##name*)arg; \
if(WAIT_EVENT(args.event)) { \
COTASK_##name(args.args); \
} \
return NULL; \
} \
static void COTASK_##name(COARGS_##name ARGS)
#define INVOKE_TASK(name, ...) do { \
(void)COTASK_UNUSED_CHECK_##name; \
COARGS_##name _coargs = { __VA_ARGS__ }; \
cosched_new_task(_cosched_global, COTASKTHUNK_##name, &_coargs); \
} while(0)
#define INVOKE_TASK_WHEN(event, name, ...) do { \
(void)COTASK_UNUSED_CHECK_##name; \
COARGSCOND_##name _coargs = { event, { __VA_ARGS__ } }; \
cosched_new_task(_cosched_global, COTASKTHUNKCOND_##name, &_coargs); \
} while(0)
#define YIELD cotask_yield(NULL)
#define WAIT(delay) do { int _delay = (delay); while(_delay-- > 0) YIELD; } while(0)
#define WAIT_EVENT(e) cotask_wait_event((e), NULL)
// 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)

View file

@ -58,6 +58,10 @@ static void fix_pos0_visual(Enemy *e) {
}
static inline int enemy_call_logic_rule(Enemy *e, int t) {
if(t == EVENT_KILLED) {
coevent_signal(&e->events.killed);
}
if(e->logic_rule) {
return e->logic_rule(e, t);
}
@ -136,6 +140,7 @@ static void* _delete_enemy(ListAnchor *enemies, List* enemy, void *arg) {
}
enemy_call_logic_rule(e, EVENT_DEATH);
coevent_cancel(&e->events.killed);
ent_unregister(&e->ent);
objpool_release(stage_object_pools.enemies, alist_unlink(enemies, enemy));

View file

@ -15,6 +15,7 @@
#include "projectile.h"
#include "objectpool.h"
#include "entity.h"
#include "coroutine.h"
#ifdef DEBUG
#define ENEMY_DEBUG
@ -39,21 +40,25 @@ struct Enemy {
cmplx pos;
cmplx pos0;
cmplx pos0_visual;
long birthtime;
int dir;
bool moving;
cmplx args[RULE_ARGC];
EnemyLogicRule logic_rule;
EnemyVisualRule visual_rule;
struct {
CoEvent killed;
} events;
int birthtime;
int dir;
float spawn_hp;
float hp;
cmplx args[RULE_ARGC];
float alpha;
bool moving;
#ifdef ENEMY_DEBUG
DebugInfo debug;
#endif

View file

@ -37,6 +37,23 @@ typedef enum {
ITEM_LAST = ITEM_LIFE,
} ItemType;
typedef union ItemCounts {
struct {
// CAUTION: must match enum order!
int piv;
int points;
int power_mini;
int power;
int surge;
int voltage;
int bomb_fragment;
int life_fragment;
int bomb;
int life;
};
int as_array[ITEM_LAST - ITEM_FIRST];
} ItemCounts;
struct Item {
ENTITY_INTERFACE_NAMED(Item, ent);

View file

@ -16,10 +16,67 @@ static CoSched *cotest_sched;
static void cotest_stub_proc(void) { }
TASK(drop_items, { complex *pos; ItemCounts items; }) {
complex p = *ARGS.pos;
for(int i = 0; i < ITEM_LAST - ITEM_FIRST; ++i) {
for(int j = ARGS.items.as_array[i]; j; --j) {
spawn_item(p, i + ITEM_FIRST);
}
}
}
TASK(wait_event_test, { Enemy *e; int rounds; int delay; int cnt; int cnt_inc; }) {
// WAIT_EVENT yields until the event fires.
// Returns true if the event was signaled, false if it was canceled.
// All waiting tasks will resume right after either of those occur, in the
// order they started waiting.
if(!WAIT_EVENT(&ARGS.e->events.killed)) {
// Event canceled? Nothing to do here.
return;
}
// Event signaled. Since this is an enemy death event, e will be invalid
// in the next frame. Let's save its position while we can.
complex pos = ARGS.e->pos;
while(ARGS.rounds--) {
WAIT(ARGS.delay);
double angle_ofs = frand() * M_PI * 2;
for(int i = 0; i < ARGS.cnt; ++i) {
complex aim = cexp(I * (angle_ofs + M_PI * 2.0 * i / (double)ARGS.cnt));
PROJECTILE(
.pos = pos,
.proto = pp_crystal,
.color = RGBA(i / (double)ARGS.cnt, 0.0, 1.0 - i / (double)ARGS.cnt, 0.0),
.rule = asymptotic,
.args = { 2 * aim, 5 },
);
WAIT(1);
}
ARGS.cnt += ARGS.cnt_inc;
}
}
TASK(test_enemy, { double hp; complex pos; complex dir; }) {
Enemy *e = create_enemy1c(ARGS.pos, ARGS.hp, BigFairy, NULL, 0);
TASK_BIND(e);
INVOKE_TASK_WHEN(&e->events.killed, drop_items, &e->pos, {
.life_fragment = 1,
.bomb_fragment = 1,
.power = 3,
.points = 5,
});
INVOKE_TASK(wait_event_test, e, 3, 10, 15, 3);
YIELD;
while(true) {