task system tweaks and early Stage 1 port attempt

This commit is contained in:
Andrei Alexeyev 2019-07-24 20:40:19 +03:00
parent 76aef22e23
commit 4e4224dc59
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
10 changed files with 309 additions and 45 deletions

2
external/koishi vendored

@ -1 +1 @@
Subproject commit 47e63b57018a62a300d797ea518578850c60730b
Subproject commit 7d66fcd750285059aa3ece6094c3fae4f583d57e

View file

@ -204,9 +204,15 @@ CoStatus cotask_status(CoTask *task) {
return koishi_state(&task->ko);
}
void cotask_bind_to_entity(CoTask *task, EntityInterface *ent) {
EntityInterface *(cotask_bind_to_entity)(CoTask *task, EntityInterface *ent) {
assert(task->bound_ent.ent == 0);
if(ent == NULL) {
koishi_die(NULL);
}
task->bound_ent = ent_box(ent);
return ent;
}
void cotask_set_finalizer(CoTask *task, CoTaskFunc finalizer, void *arg) {
@ -346,3 +352,18 @@ DEFINE_EXTERN_TASK(_cancel_task_helper) {
cotask_cancel(task);
}
}
#include <projectile.h>
#include <laser.h>
#include <item.h>
#include <enemy.h>
#include <boss.h>
#include <player.h>
#define ENT_TYPE(typename, id) \
typename *_cotask_bind_to_entity_##typename(CoTask *task, typename *ent) { \
return ENT_CAST((cotask_bind_to_entity)(task, ent ? &ent->entity_interface : NULL), typename); \
}
ENT_TYPES
#undef ENT_TYPE

View file

@ -58,7 +58,7 @@ 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);
EntityInterface *cotask_bind_to_entity(CoTask *task, EntityInterface *ent) attr_returns_nonnull;
void cotask_set_finalizer(CoTask *task, CoTaskFunc finalizer, void *arg);
BoxedTask cotask_box(CoTask *task);
@ -87,6 +87,9 @@ static inline attr_must_inline void cosched_set_invoke_target(CoSched *sched) {
#define ARGS (*_cotask_args)
#define NO_ARGS { char _dummy_0; }
#define ARGS_STRUCT(argstruct) { struct argstruct; char _dummy_1; }
#define TASK_COMMON_PRIVATE_DECLARATIONS(name) \
/* user-defined task body */ \
static void COTASK_##name(TASK_ARGS(name) *_cotask_args); \
@ -97,7 +100,7 @@ static inline attr_must_inline void cosched_set_invoke_target(CoSched *sched) {
/* produce warning if the task is never used */ \
linkage char COTASK_UNUSED_CHECK_##name; \
/* user-defined type of args struct */ \
struct TASK_ARGS_NAME(name) argstruct; \
struct TASK_ARGS_NAME(name) ARGS_STRUCT(argstruct); \
/* type of internal args struct for INVOKE_TASK_DELAYED */ \
struct TASK_ARGSDELAY_NAME(name) { TASK_ARGS(name) real_args; int delay; }; \
/* type of internal args struct for INVOKE_TASK_WHEN */ \
@ -227,27 +230,33 @@ static inline attr_must_inline void cosched_set_invoke_target(CoSched *sched) {
/* begin finalizer body definition */ \
static void COTASKFINALIZER_##name(TASK_ARGS(name) *_cotask_args)
#define INVOKE_TASK(name, ...) ( \
#define INVOKE_TASK_(name, ...) ( \
(void)COTASK_UNUSED_CHECK_##name, \
cosched_new_task(_cosched_global, COTASKTHUNK_##name, \
&(TASK_ARGS(name)) { __VA_ARGS__ } \
) \
)
#define INVOKE_TASK_WHEN(_event, name, ...) ( \
#define INVOKE_TASK(...) INVOKE_TASK_(__VA_ARGS__, ._dummy_1 = 0)
#define INVOKE_TASK_WHEN_(_event, name, ...) ( \
(void)COTASK_UNUSED_CHECK_##name, \
cosched_new_task(_cosched_global, COTASKTHUNKCOND_##name, \
&(TASK_ARGSCOND(name)) { .real_args = { __VA_ARGS__ }, .event = (_event) } \
) \
)
#define INVOKE_TASK_DELAYED(_delay, name, ...) ( \
#define INVOKE_TASK_WHEN(_event, ...) INVOKE_TASK_WHEN_(_event, __VA_ARGS__, ._dummy_1 = 0)
#define INVOKE_TASK_DELAYED_(_delay, name, ...) ( \
(void)COTASK_UNUSED_CHECK_##name, \
cosched_new_task(_cosched_global, COTASKTHUNKDELAY_##name, \
&(TASK_ARGSDELAY(name)) { .real_args = { __VA_ARGS__ }, .delay = (_delay) } \
) \
)
#define INVOKE_TASK_DELAYED(_delay, ...) INVOKE_TASK_DELAYED_(_delay, __VA_ARGS__, ._dummy_1 = 0)
DECLARE_EXTERN_TASK(_cancel_task_helper, { BoxedTask task; });
#define CANCEL_TASK_WHEN(_event, _task) INVOKE_TASK_WHEN(_event, _cancel_task_helper, _task)
@ -263,6 +272,24 @@ DECLARE_EXTERN_TASK(_cancel_task_helper, { BoxedTask task; });
#define BYIELD do { YIELD; CHECK_BREAK; } while(0)
#define BWAIT(frames) do { WAIT(frames); CHECK_BREAK; } while(0)
#define TASK_BIND(ent) cotask_bind_to_entity(cotask_active(), &ent->entity_interface)
#define ENT_TYPE(typename, id) \
struct typename; \
struct typename *_cotask_bind_to_entity_##typename(CoTask *task, struct typename *ent);
ENT_TYPES
#undef ENT_TYPE
#define cotask_bind_to_entity(task, ent) (_Generic((ent), \
Projectile*: _cotask_bind_to_entity_Projectile, \
Laser*: _cotask_bind_to_entity_Laser, \
Enemy*: _cotask_bind_to_entity_Enemy, \
Boss*: _cotask_bind_to_entity_Boss, \
Player*: _cotask_bind_to_entity_Player, \
Item*: _cotask_bind_to_entity_Item, \
EntityInterface*: cotask_bind_to_entity \
)(task, ent))
#define TASK_BIND(box) cotask_bind_to_entity(cotask_active(), ENT_UNBOX(box))
#define TASK_BIND_UNBOXED(ent) cotask_bind_to_entity(cotask_active(), ent)
#endif // IGUARD_coroutine_h

View file

@ -260,3 +260,15 @@ EntityInterface *ent_unbox(BoxedEntity box) {
return NULL;
}
#define ENT_TYPE(typename, id) \
Boxed##typename _ent_box_##typename(struct typename *ent) { \
return (Boxed##typename) { .as_generic = ent_box(&ent->entity_interface) };\
} \
struct typename *_ent_unbox_##typename(Boxed##typename box) { \
EntityInterface *e = ent_unbox(box.as_generic); \
return e ? ENT_CAST(e, typename) : NULL; \
}
ENT_TYPES
#undef ENT_TYPE

View file

@ -110,11 +110,6 @@ struct EntityInterface {
ENTITY_INTERFACE_BASE(EntityInterface);
};
struct BoxedEntity {
uintptr_t ent; // not an actual pointer to avert the temptation to use it directly.
uint_fast32_t spawn_id;
};
INLINE const char* ent_type_name(EntityType type) {
switch(type) {
#define ENT_TYPE(typename, id) case id: return #id;
@ -158,18 +153,47 @@ void ent_unhook_pre_draw(EntityDrawHookCallback callback);
void ent_hook_post_draw(EntityDrawHookCallback callback, void *arg);
void ent_unhook_post_draw(EntityDrawHookCallback callback);
struct BoxedEntity {
uintptr_t ent; // not an actual pointer to avert the temptation to use it directly.
uint_fast32_t spawn_id;
};
BoxedEntity ent_box(EntityInterface *ent);
EntityInterface *ent_unbox(BoxedEntity box);
#define ENT_BOX(ent) ent_box(&(ent)->entity_interface)
#define ENT_TYPE(typename, id) \
typedef union Boxed##typename { \
BoxedEntity as_generic; \
struct { \
uintptr_t ent; \
uint_fast32_t spawn_id; \
}; \
} Boxed##typename; \
struct typename; \
Boxed##typename _ent_box_##typename(struct typename *ent); \
struct typename *_ent_unbox_##typename(Boxed##typename box); \
#if defined(USE_GNU_EXTENSIONS) && defined(DEBUG)
#define ENT_UNBOX(h, typename) (__extension__ ({ \
EntityInterface *_unboxed_ent = ent_unbox(h); \
_unboxed_ent ? ENT_CAST(_unboxed_ent, typename) : NULL; \
}))
#else
#define ENT_UNBOX(h, typename) CASTPTR_ASSUME_ALIGNED(ent_unbox(h), typename)
#endif
ENT_TYPES
#undef ENT_TYPE
#define ENT_BOX(ent) (_Generic((ent), \
Projectile*: _ent_box_Projectile, \
Laser*: _ent_box_Laser, \
Enemy*: _ent_box_Enemy, \
Boss*: _ent_box_Boss, \
Player*: _ent_box_Player, \
Item*: _ent_box_Item, \
EntityInterface*: ent_box \
)(ent))
#define ENT_UNBOX(box) (_Generic((box), \
BoxedProjectile: _ent_unbox_Projectile, \
BoxedLaser: _ent_unbox_Laser, \
BoxedEnemy: _ent_unbox_Enemy, \
BoxedBoss: _ent_unbox_Boss, \
BoxedPlayer: _ent_unbox_Player, \
BoxedItem: _ent_unbox_Item, \
BoxedEntity: ent_unbox \
)(box))
#endif // IGUARD_entity_h

View file

@ -36,4 +36,10 @@ MOVE_FUNC move_asymptotic(complex vel0, complex vel1, complex retention) {
return (MoveParams) { vel0, vel1 * (1 - retention), retention };
}
MOVE_FUNC move_asymptotic_simple(complex vel, double boost_factor) {
// NOTE: this matches the old asymptotic rule semantics exactly
double retention = 0.8;
return move_asymptotic(vel * (1 + boost_factor * retention), vel, retention);
}
#endif // IGUARD_move_h

View file

@ -733,16 +733,25 @@ inline bool stage_should_yield(void) {
return (global.boss && !boss_is_fleeing(global.boss)) || dialog_is_active(global.dialog);
}
void stage_yield(void) {
int stage_yield(void) {
int num_yields = 0;
do {
cotask_yield(NULL);
++num_yields;
} while(stage_should_yield());
return num_yields;
}
void stage_wait(int delay) {
int stage_wait(int delay) {
int num_yields = 0;
while(delay-- > 0) {
stage_yield();
num_yields += stage_yield();
}
return num_yields;
}
static LogicFrameAction stage_logic_frame(void *arg) {

View file

@ -150,8 +150,8 @@ bool stage_is_cleared(void);
void stage_unlock_bgm(const char *bgm);
bool stage_should_yield(void);
void stage_yield(void);
void stage_wait(int delay);
int stage_yield(void);
int stage_wait(int delay);
#include "stages/stage1.h"
#include "stages/stage2.h"

View file

@ -17,19 +17,19 @@ TASK(laserproj_death, { Projectile *p; }) {
spawn_projectile_clear_effect(ARGS.p);
}
TASK(laserize_proj, { Projectile *p; int t; }) {
TASK_BIND(ARGS.p);
TASK(laserize_proj, { BoxedProjectile p; int t; }) {
Projectile *p = TASK_BIND(ARGS.p);
WAIT(ARGS.t);
complex pos = ARGS.p->pos;
double a = ARGS.p->angle;
Color clr = ARGS.p->color;
kill_projectile(ARGS.p);
complex pos = p->pos;
double a = p->angle;
Color clr = p->color;
kill_projectile(p);
complex aim = 12 * cexp(I * a);
create_laserline(pos, aim, 60, 80, &clr);
Projectile *p = PROJECTILE(
p = PROJECTILE(
.pos = pos,
.proto = pp_ball,
.color = &clr,
@ -39,13 +39,15 @@ TASK(laserize_proj, { Projectile *p; int t; }) {
INVOKE_TASK_WHEN(&p->events.killed, laserproj_death, p);
}
TASK(wait_event_test, { Enemy *e; int rounds; int delay; int cnt; int cnt_inc; }) {
TASK(wait_event_test, { BoxedEnemy 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)) {
Enemy *e = ENT_UNBOX(ARGS.e);
if(!WAIT_EVENT(&e->events.killed)) {
// Event canceled? Nothing to do here.
log_debug("[%p] leave (canceled)", (void*)cotask_active());
return;
@ -53,7 +55,7 @@ TASK(wait_event_test, { Enemy *e; int rounds; int delay; int cnt; int cnt_inc; }
// 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;
complex pos = e->pos;
while(ARGS.rounds--) {
WAIT(ARGS.delay);
@ -81,8 +83,7 @@ TASK_WITH_FINALIZER(test_enemy, {
double hp; complex pos; complex dir;
struct { int x; } for_finalizer;
}) {
Enemy *e = create_enemy1c(ARGS.pos, ARGS.hp, BigFairy, NULL, 0);
TASK_BIND(e);
Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, ARGS.hp, BigFairy, NULL, 0));
INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, {
.life_fragment = 1,
@ -91,7 +92,7 @@ TASK_WITH_FINALIZER(test_enemy, {
.points = 5,
});
INVOKE_TASK(wait_event_test, e, 3, 10, 15, 3);
INVOKE_TASK(wait_event_test, ENT_BOX(e), 3, 10, 15, 3);
for(;;) {
YIELD;
@ -122,7 +123,7 @@ TASK_WITH_FINALIZER(test_enemy, {
.max_viewport_dist = 128,
);
INVOKE_TASK(laserize_proj, p, 40);
INVOKE_TASK(laserize_proj, ENT_BOX(p), 40);
}
WAIT(2);
@ -137,7 +138,7 @@ TASK_FINALIZER(test_enemy) {
log_debug("finalizer called (x = %i)", ARGS.for_finalizer.x);
}
TASK(stage_main, { int ignored; }) {
TASK(stage_main, NO_ARGS) {
YIELD;
stage_wait(30);
@ -152,7 +153,7 @@ TASK(stage_main, { int ignored; }) {
}
static void cotest_begin(void) {
INVOKE_TASK(stage_main, 0);
INVOKE_TASK(stage_main);
}
StageProcs corotest_procs = {

View file

@ -1237,14 +1237,178 @@ static int proj_rotate(Projectile *p, int t) {
}
#endif
TASK(burst_fairy, { complex pos; complex dir; }) {
Enemy *e = create_enemy1c(ARGS.pos, 700, Fairy, stage1_burst, ARGS.dir);
TASK_BIND_UNBOXED(e);
}
struct circletoss_shared {
complex velocity;
complex exit_accel;
int exit_time;
};
TASK(circletoss_move, { BoxedEnemy e; struct circletoss_shared *shared; }) {
Enemy *e = TASK_BIND(ARGS.e);
for(int i = 0;; ++i) {
e->pos += ARGS.shared->velocity;
if(i >= ARGS.shared->exit_time) {
ARGS.shared->velocity += ARGS.shared->exit_accel;
}
YIELD;
}
}
TASK(circletoss_shoot_circle, { BoxedEnemy e; int duration; int interval; struct circletoss_shared *shared; }) {
Enemy *e = TASK_BIND(ARGS.e);
int cnt = ARGS.duration / ARGS.interval;
double angle_step = 2 * M_PI / cnt;
for(int i = 0; i < cnt; ++i) {
play_loop("shot1_loop");
ARGS.shared->velocity *= 0.8;
complex aim = cdir(angle_step * i);
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = RGB(0.6, 0.2, 0.7),
.move = move_asymptotic_simple(2 * aim, i * 0.5),
);
WAIT(ARGS.interval);
}
}
TASK(circletoss_shoot_toss, { BoxedEnemy e; int times; int duration; int period; }) {
Enemy *e = TASK_BIND(ARGS.e);
while(ARGS.times--) {
for(int i = ARGS.duration; i--;) {
play_loop("shot1_loop");
double aim_angle = carg(global.plr.pos - e->pos);
aim_angle += 0.05 * global.diff * nfrand();
complex aim = cdir(aim_angle);
aim *= 1 + frand() * 2;
PROJECTILE(
.proto = pp_thickrice,
.pos = e->pos,
.color = RGB(0.2, 0.4, 0.8),
.move = move_asymptotic_simple(aim, 3),
);
WAIT(1);
}
WAIT(ARGS.period - ARGS.duration);
}
}
TASK(circletoss_fairy, { complex pos; complex velocity; complex exit_accel; int exit_time; }) {
Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 1500, BigFairy, NULL, 0));
struct circletoss_shared shared = {
.velocity = ARGS.velocity,
.exit_accel = 0.03 * ARGS.exit_accel - 0.04 * I,
.exit_time = ARGS.exit_time,
};
INVOKE_TASK(circletoss_move, ENT_BOX(e), &shared);
INVOKE_TASK_DELAYED(60, circletoss_shoot_circle, ENT_BOX(e),
.duration = 40,
.interval = 2 + (global.diff < D_Hard),
.shared = &shared
);
if(global.diff > D_Easy) {
INVOKE_TASK_DELAYED(90, circletoss_shoot_toss, ENT_BOX(e),
.times = 4,
.period = 150,
.duration = 5 + 7 * global.diff
);
}
for(;;) YIELD;
}
// opening. projectile bursts
TASK(burst_fairies_1, NO_ARGS) {
for(int i = 3; i--;) {
INVOKE_TASK(burst_fairy, VIEWPORT_W/2 + 70, 1 + 0.6*I);
INVOKE_TASK(burst_fairy, VIEWPORT_W/2 - 70, -1 + 0.6*I);
stage_wait(25);
}
}
// more bursts. fairies move / \ like
TASK(burst_fairies_2, NO_ARGS) {
for(int i = 3; i--;) {
double ofs = 70 + i * 40;
INVOKE_TASK(burst_fairy, ofs, 1 + 0.6*I);
stage_wait(15);
INVOKE_TASK(burst_fairy, VIEWPORT_W - ofs, -1 + 0.6*I);
stage_wait(15);
}
}
// swirl, sine pass
TASK(sinepass_swirls, NO_ARGS) {
const int cnt = 32;
for(int i = 0; i < cnt; ++i) {
tsrand_fill(2);
create_enemy2c(VIEWPORT_W*(i&1) + afrand(0)*100.0*I + 70.0*I, 100, Swirl, stage1_sinepass, 3.5*(1-2*(i&1)), afrand(1)*7.0*I);
stage_wait(20);
}
}
// big fairies, circle + projectile toss
TASK(circletoss_fairies_1, NO_ARGS) {
for(int i = 0; i < 2; ++i) {
INVOKE_TASK(circletoss_fairy,
.pos = VIEWPORT_W * i + VIEWPORT_H / 3 * I,
.velocity = 2 - 4 * i - 0.3 * I,
.exit_accel = 1 - 2 * i,
.exit_time = (global.diff > D_Easy) ? 500 : 240
);
stage_wait(50);
}
}
TASK(stage_timeline, NO_ARGS) {
stage_start_bgm("stage1");
stage_set_voltage_thresholds(50, 125, 300, 600);
YIELD;
stage_wait(100);
INVOKE_TASK(burst_fairies_1);
stage_wait(140);
INVOKE_TASK(burst_fairies_2);
stage_wait(200);
INVOKE_TASK(sinepass_swirls);
stage_wait(20);
INVOKE_TASK(circletoss_fairies_1);
}
void stage1_events(void) {
TIMER(&global.timer);
AT(0) {
stage_start_bgm("stage1");
stage_set_voltage_thresholds(50, 125, 300, 600);
INVOKE_TASK(stage_timeline);
}
return;
#ifdef BULLET_TEST
if(!global.projs) {
PROJECTILE(