experimental event system for coroutine-based stages
This commit is contained in:
parent
1579082e30
commit
9fe54e0335
7 changed files with 176 additions and 11 deletions
2
external/koishi
vendored
2
external/koishi
vendored
|
@ -1 +1 @@
|
|||
Subproject commit cc6641ee7a3b0c17df4bfa56e6367bdb2e3ffc94
|
||||
Subproject commit 47e63b57018a62a300d797ea518578850c60730b
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
17
src/enemy.h
17
src/enemy.h
|
@ -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
|
||||
|
|
17
src/item.h
17
src/item.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue