Add generic type-safe facility for dynamic arrays (#207)
Replace most ad-hoc opencoded dynamic arrays across the codebase
This commit is contained in:
parent
54c93d8923
commit
0cbc86c66e
35 changed files with 739 additions and 521 deletions
|
@ -648,48 +648,26 @@ int cotask_wait(int delay) {
|
|||
return cotask_wait_init(task_data, COTASK_WAIT_NONE).frames;
|
||||
}
|
||||
|
||||
static bool subscribers_array_predicate(const void *pelem, void *userdata) {
|
||||
return cotask_unbox(*(const BoxedTask*)pelem);
|
||||
}
|
||||
|
||||
static void coevent_cleanup_subscribers(CoEvent *evt) {
|
||||
if(evt->num_subscribers == 0) {
|
||||
if(evt->subscribers.num_elements == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint num_subs = evt->num_subscribers;
|
||||
BoxedTask new_subs[num_subs];
|
||||
BoxedTask *p_new_subs = new_subs;
|
||||
|
||||
for(uint i = 0; i < num_subs; ++i) {
|
||||
if(cotask_unbox(evt->subscribers[i])) {
|
||||
*p_new_subs++ = evt->subscribers[i];
|
||||
}
|
||||
}
|
||||
|
||||
num_subs = p_new_subs - new_subs;
|
||||
|
||||
EVT_DEBUG("Event %p num_subscribers %u -> %u", (void*)evt, evt->num_subscribers, num_subs);
|
||||
|
||||
memcpy(evt->subscribers, new_subs, sizeof(new_subs[0]) * num_subs);
|
||||
evt->num_subscribers = num_subs;
|
||||
attr_unused uint prev_num_subs = evt->subscribers.num_elements;
|
||||
dynarray_filter(&evt->subscribers, subscribers_array_predicate, NULL);
|
||||
attr_unused uint new_num_subs = evt->subscribers.num_elements;
|
||||
EVT_DEBUG("Event %p num subscribers %u -> %u", (void*)evt, prev_num_subs, new_num_subs);
|
||||
}
|
||||
|
||||
static void coevent_add_subscriber(CoEvent *evt, CoTask *task) {
|
||||
EVT_DEBUG("Event %p (num_subscribers=%u; num_subscribers_allocated=%u)", (void*)evt, evt->num_subscribers, evt->num_subscribers_allocated);
|
||||
EVT_DEBUG("Subscriber: %s", task->debug_label);
|
||||
|
||||
evt->num_subscribers++;
|
||||
assert(evt->num_subscribers != 0);
|
||||
|
||||
if(evt->num_subscribers >= evt->num_subscribers_allocated) {
|
||||
if(!evt->num_subscribers_allocated) {
|
||||
evt->num_subscribers_allocated = 4;
|
||||
} else {
|
||||
evt->num_subscribers_allocated *= 2;
|
||||
assert(evt->num_subscribers_allocated != 0);
|
||||
}
|
||||
|
||||
evt->subscribers = realloc(evt->subscribers, sizeof(*evt->subscribers) * evt->num_subscribers_allocated);
|
||||
}
|
||||
|
||||
evt->subscribers[evt->num_subscribers - 1] = cotask_box(task);
|
||||
*dynarray_append_with_min_capacity(&evt->subscribers, 4) = cotask_box(task);
|
||||
}
|
||||
|
||||
CoWaitResult cotask_wait_event(CoEvent *evt, void *arg) {
|
||||
|
@ -771,10 +749,10 @@ void coevent_signal(CoEvent *evt) {
|
|||
EVT_DEBUG("Signal event %p (uid = %u; num_signaled = %u)", (void*)evt, evt->unique_id, evt->num_signaled);
|
||||
assert(evt->num_signaled != 0);
|
||||
|
||||
if(evt->num_subscribers) {
|
||||
BoxedTask subs_snapshot[evt->num_subscribers];
|
||||
memcpy(subs_snapshot, evt->subscribers, sizeof(subs_snapshot));
|
||||
evt->num_subscribers = 0;
|
||||
if(evt->subscribers.num_elements) {
|
||||
BoxedTask subs_snapshot[evt->subscribers.num_elements];
|
||||
memcpy(subs_snapshot, evt->subscribers.data, sizeof(subs_snapshot));
|
||||
evt->subscribers.num_elements = 0;
|
||||
coevent_wake_subscribers(evt, ARRAY_SIZE(subs_snapshot), subs_snapshot);
|
||||
}
|
||||
}
|
||||
|
@ -798,28 +776,14 @@ void coevent_cancel(CoEvent *evt) {
|
|||
evt->num_signaled = 0;
|
||||
evt->unique_id = 0;
|
||||
|
||||
if(evt->num_subscribers) {
|
||||
assume(evt->subscribers != NULL);
|
||||
assume(evt->num_subscribers_allocated > 0);
|
||||
BoxedTask subs_snapshot[evt->num_subscribers];
|
||||
memcpy(subs_snapshot, evt->subscribers, sizeof(subs_snapshot));
|
||||
EVT_DEBUG("[%lu] Snapshot %zu subscribers at %p", ev, ARRAY_SIZE(subs_snapshot), (void*)subs_snapshot);
|
||||
evt->num_subscribers = 0;
|
||||
evt->num_subscribers_allocated = 0;
|
||||
EVT_DEBUG("[%lu] FREE (*%p)->subscribers (%p)", ev, (void*)evt, (void*)evt->subscribers);
|
||||
free(evt->subscribers);
|
||||
evt->subscribers = NULL;
|
||||
if(evt->subscribers.num_elements) {
|
||||
BoxedTask subs_snapshot[evt->subscribers.num_elements];
|
||||
memcpy(subs_snapshot, evt->subscribers.data, sizeof(subs_snapshot));
|
||||
dynarray_free_data(&evt->subscribers);
|
||||
coevent_wake_subscribers(evt, ARRAY_SIZE(subs_snapshot), subs_snapshot);
|
||||
// CAUTION: no modifying evt after this point, it may be invalidated
|
||||
} else if(evt->subscribers) {
|
||||
assume(evt->subscribers != NULL);
|
||||
assume(evt->num_subscribers_allocated > 0);
|
||||
evt->num_subscribers_allocated = 0;
|
||||
free(evt->subscribers);
|
||||
evt->subscribers = NULL;
|
||||
} else {
|
||||
assume(evt->num_subscribers_allocated == 0);
|
||||
assume(evt->subscribers == NULL);
|
||||
dynarray_free_data(&evt->subscribers);
|
||||
}
|
||||
|
||||
EVT_DEBUG("[%lu] END Cancel event %p", ev, (void*)evt);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "entity.h"
|
||||
#include "util/debug.h"
|
||||
#include "dynarray.h"
|
||||
|
||||
#include <koishi.h>
|
||||
|
||||
|
@ -46,16 +47,14 @@ typedef struct CoEvent {
|
|||
// ListAnchor subscribers;
|
||||
// FIXME: Is there a better way than a dynamic array?
|
||||
// An intrusive linked list just isn't robust enough.
|
||||
BoxedTask *subscribers;
|
||||
DYNAMIC_ARRAY(BoxedTask) subscribers;
|
||||
uint32_t unique_id;
|
||||
uint16_t num_signaled;
|
||||
uint8_t num_subscribers;
|
||||
uint8_t num_subscribers_allocated;
|
||||
uint32_t num_signaled;
|
||||
} CoEvent;
|
||||
|
||||
typedef struct CoEventSnapshot {
|
||||
uint32_t unique_id;
|
||||
uint16_t num_signaled;
|
||||
uint32_t num_signaled;
|
||||
} CoEventSnapshot;
|
||||
|
||||
#define COEVENTS_ARRAY(...) \
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "resource/model.h"
|
||||
#include "renderer/api.h"
|
||||
#include "util/glm.h"
|
||||
#include "dynarray.h"
|
||||
|
||||
typedef struct CreditsEntry {
|
||||
char **data;
|
||||
|
@ -23,8 +24,7 @@ typedef struct CreditsEntry {
|
|||
} CreditsEntry;
|
||||
|
||||
static struct {
|
||||
CreditsEntry *entries;
|
||||
int ecount;
|
||||
DYNAMIC_ARRAY(CreditsEntry) entries;
|
||||
float panelalpha;
|
||||
int end;
|
||||
bool skipable;
|
||||
|
@ -49,6 +49,8 @@ static void credits_fill(void) {
|
|||
// Tuck V's YouTube: https://www.youtube.com/channel/UCaw73cuHLnFCSpjOtt_9pyg
|
||||
// InsideI's bandcamp: https://vnutriya.bandcamp.com/
|
||||
|
||||
dynarray_ensure_capacity(&credits.entries, 24);
|
||||
|
||||
credits_add("Taisei Project\nbrought to you by…", HEADER_TIME);
|
||||
|
||||
credits_add((
|
||||
|
@ -183,8 +185,7 @@ static void credits_add(char *data, int time) {
|
|||
|
||||
assert(time > CREDITS_ENTRY_FADEOUT);
|
||||
|
||||
credits.entries = realloc(credits.entries, (++credits.ecount) * sizeof(CreditsEntry));
|
||||
e = &(credits.entries[credits.ecount-1]);
|
||||
e = dynarray_append(&credits.entries);
|
||||
e->time = time - CREDITS_ENTRY_FADEOUT;
|
||||
e->lines = 1;
|
||||
|
||||
|
@ -290,7 +291,7 @@ static void credits_draw_entry(CreditsEntry *e) {
|
|||
int time = global.frames - 200;
|
||||
float fadein = 1, fadeout = 1;
|
||||
|
||||
for(CreditsEntry *o = credits.entries; o != e; ++o) {
|
||||
for(CreditsEntry *o = credits.entries.data; o != e; ++o) {
|
||||
time -= o->time + CREDITS_ENTRY_FADEOUT;
|
||||
}
|
||||
|
||||
|
@ -398,9 +399,9 @@ static void credits_draw(void) {
|
|||
|
||||
r_shader_standard();
|
||||
|
||||
for(int i = 0; i < credits.ecount; ++i) {
|
||||
credits_draw_entry(&(credits.entries[i]));
|
||||
}
|
||||
dynarray_foreach_elem(&credits.entries, CreditsEntry *e, {
|
||||
credits_draw_entry(e);
|
||||
});
|
||||
|
||||
r_mat_mv_pop();
|
||||
|
||||
|
@ -432,15 +433,14 @@ static void credits_process(void) {
|
|||
}
|
||||
|
||||
static void credits_free(void) {
|
||||
int i, j;
|
||||
for(i = 0; i < credits.ecount; ++i) {
|
||||
CreditsEntry *e = &(credits.entries[i]);
|
||||
for(j = 0; j < e->lines; ++j)
|
||||
free(e->data[j]);
|
||||
dynarray_foreach_elem(&credits.entries, CreditsEntry *e, {
|
||||
for(int i = 0; i < e->lines; ++i) {
|
||||
free(e->data[i]);
|
||||
}
|
||||
free(e->data);
|
||||
}
|
||||
});
|
||||
|
||||
free(credits.entries);
|
||||
dynarray_free_data(&credits.entries);
|
||||
stage3d_shutdown(&stage_3d_context);
|
||||
}
|
||||
|
||||
|
|
96
src/dynarray.c
Normal file
96
src/dynarray.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "dynarray.h"
|
||||
#include "util.h"
|
||||
|
||||
// #define DYNARRAY_DEBUG
|
||||
|
||||
#ifdef DYNARRAY_DEBUG
|
||||
#undef DYNARRAY_DEBUG
|
||||
#define DYNARRAY_DEBUG(darr, fmt, ...) log_debug("[%p] " fmt, (void*)(darr), __VA_ARGS__)
|
||||
#else
|
||||
#define DYNARRAY_DEBUG(...) ((void)0)
|
||||
#endif
|
||||
|
||||
void _dynarray_free_data(dynarray_size_t sizeof_element, DynamicArray *darr) {
|
||||
free(darr->data);
|
||||
if(darr->capacity) {
|
||||
DYNARRAY_DEBUG(darr, "%u/%u", darr->num_elements, darr->capacity);
|
||||
}
|
||||
memset(darr, 0, sizeof(*darr));
|
||||
}
|
||||
|
||||
INLINE void _dynarray_resize(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t capacity) {
|
||||
DYNARRAY_DEBUG(darr, "capacity change: %u --> %u", darr->capacity, capacity);
|
||||
darr->capacity = capacity;
|
||||
darr->data = realloc(darr->data, sizeof_element * capacity);
|
||||
}
|
||||
|
||||
void _dynarray_ensure_capacity(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t capacity) {
|
||||
if(darr->capacity < capacity) {
|
||||
_dynarray_resize(sizeof_element, darr, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
dynarray_size_t _dynarray_prepare_append_with_min_capacity(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t min_capacity) {
|
||||
dynarray_size_t num_elements = darr->num_elements;
|
||||
dynarray_size_t capacity = darr->capacity;
|
||||
|
||||
assume(num_elements >= 0);
|
||||
assume(num_elements <= capacity);
|
||||
assume(min_capacity >= 2);
|
||||
|
||||
if(capacity < min_capacity) {
|
||||
_dynarray_resize(sizeof_element, darr, min_capacity);
|
||||
} else if(num_elements == capacity) {
|
||||
capacity += capacity >> 1;
|
||||
_dynarray_resize(sizeof_element, darr, capacity);
|
||||
}
|
||||
|
||||
++darr->num_elements;
|
||||
memset((char*)darr->data + num_elements * sizeof_element, 0, sizeof_element);
|
||||
|
||||
DYNARRAY_DEBUG(darr, "elements: %u/%u", darr->num_elements, darr->capacity);
|
||||
return num_elements; // insertion index
|
||||
}
|
||||
|
||||
void _dynarray_compact(dynarray_size_t sizeof_element, DynamicArray *darr) {
|
||||
if(darr->capacity > darr->num_elements) {
|
||||
_dynarray_resize(sizeof_element, darr, darr->num_elements);
|
||||
}
|
||||
}
|
||||
|
||||
void _dynarray_set_elements(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t num_elements, void *elements) {
|
||||
_dynarray_ensure_capacity(sizeof_element, darr, num_elements);
|
||||
memcpy(&darr->data, elements, num_elements * sizeof_element);
|
||||
darr->num_elements = num_elements;
|
||||
}
|
||||
|
||||
void _dynarray_filter(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_filter_predicate_t predicate, void *userdata) {
|
||||
char *p = darr->data;
|
||||
char *end = p + sizeof_element * darr->num_elements;
|
||||
dynarray_size_t shift = 0;
|
||||
|
||||
while(p < end) {
|
||||
if(!predicate(p, userdata)) {
|
||||
shift += sizeof_element;
|
||||
--darr->num_elements;
|
||||
} else if(shift) {
|
||||
memmove(p - shift, p, sizeof_element);
|
||||
}
|
||||
|
||||
p += sizeof_element;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
#endif
|
187
src/dynarray.h
Normal file
187
src/dynarray.h
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#ifndef IGUARD_dynarray_h
|
||||
#define IGUARD_dynarray_h
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "util/macrohax.h"
|
||||
|
||||
// TODO document this mess
|
||||
|
||||
// NOTE/FIXME: this should be signed to avoid some subtle problems when
|
||||
// interacting with other signed values. In the future we should enable implicit
|
||||
// signedness conversion warnings and deal with the issue globally.
|
||||
typedef int32_t dynarray_size_t;
|
||||
|
||||
#define DYNAMIC_ARRAY_BASE(elem_type) struct { \
|
||||
elem_type *data; \
|
||||
dynarray_size_t num_elements; \
|
||||
dynarray_size_t capacity; \
|
||||
} \
|
||||
|
||||
typedef DYNAMIC_ARRAY_BASE(void) DynamicArray;
|
||||
|
||||
#define DYNAMIC_ARRAY(elem_type) union { \
|
||||
DYNAMIC_ARRAY_BASE(elem_type); \
|
||||
DynamicArray dyn_array; \
|
||||
}
|
||||
|
||||
#ifdef USE_GNU_EXTENSIONS
|
||||
#define DYNARRAY_ASSERT_VALID(darr) do { \
|
||||
static_assert( \
|
||||
__builtin_types_compatible_p(DynamicArray, __typeof__((darr)->dyn_array)), \
|
||||
"x->dyn_array must be of DynamicArray type"); \
|
||||
static_assert( \
|
||||
__builtin_offsetof(__typeof__(*(darr)), dyn_array) == 0, \
|
||||
"x->dyn_array must be the first member in struct"); \
|
||||
} while(0)
|
||||
#else
|
||||
#define DYNARRAY_ASSERT_VALID(darr) ((void)0)
|
||||
#endif
|
||||
|
||||
#ifdef USE_GNU_EXTENSIONS
|
||||
#define DYNARRAY_CAST_TO_BASE(darr) (__extension__ ({ \
|
||||
DYNARRAY_ASSERT_VALID(darr); \
|
||||
&NOT_NULL(darr)->dyn_array; \
|
||||
}))
|
||||
#else
|
||||
#define DYNARRAY_CAST_TO_BASE(darr) (&(darr)->dyn_array)
|
||||
#endif
|
||||
|
||||
#ifdef USE_GNU_EXTENSIONS
|
||||
#define DYNARRAY_CHECK_ELEMENT_TYPE(darr, elem) do { \
|
||||
static_assert( \
|
||||
__builtin_types_compatible_p(__typeof__(elem), __typeof__(*(darr)->data)), \
|
||||
"Dynamic array element type mismatch" \
|
||||
); \
|
||||
} while(0)
|
||||
#else
|
||||
#define DYNARRAY_CHECK_ELEMENT_TYPE(darr, elem) do { \
|
||||
static_assert( \
|
||||
sizeof(elem) == sizeof(*(darr)->data), \
|
||||
"Dynamic array element size mismatch" \
|
||||
); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
#define DYNARRAY_ELEM_SIZE(darr) sizeof(*(darr)->data)
|
||||
|
||||
#define _dynarray_dispatch_func(func, darr, ...) \
|
||||
_dynarray_##func(DYNARRAY_ELEM_SIZE(darr), DYNARRAY_CAST_TO_BASE(darr), __VA_ARGS__)
|
||||
|
||||
#define _dynarray_dispatch_func_noargs(func, darr) \
|
||||
_dynarray_##func(DYNARRAY_ELEM_SIZE(darr), DYNARRAY_CAST_TO_BASE(darr))
|
||||
|
||||
void _dynarray_free_data(dynarray_size_t sizeof_element, DynamicArray *darr) attr_nonnull_all;
|
||||
#define dynarray_free_data(darr) \
|
||||
_dynarray_dispatch_func_noargs(free_data, darr)
|
||||
|
||||
void _dynarray_ensure_capacity(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t capacity) attr_nonnull_all;
|
||||
#define dynarray_ensure_capacity(darr, capacity) \
|
||||
_dynarray_dispatch_func(ensure_capacity, darr, capacity)
|
||||
|
||||
dynarray_size_t _dynarray_prepare_append_with_min_capacity(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t min_capacity) attr_nonnull_all;
|
||||
|
||||
/*
|
||||
* NOTE: unfortunately this evaluates `darr` more than once, but when compiling
|
||||
* with NDEBUG the assume() macro should trip a clang warning if there are
|
||||
* potential side-effects, and we always treat that warning as an error.
|
||||
*/
|
||||
#define _dynarray_append_with_min_capacity(darr, min_capacity) \
|
||||
dynarray_get_ptr(darr, _dynarray_dispatch_func(prepare_append_with_min_capacity, darr, min_capacity))
|
||||
|
||||
#ifdef USE_GNU_EXTENSIONS
|
||||
#define dynarray_append_with_min_capacity(darr, min_capacity) (__extension__ ({ \
|
||||
assume((darr) != NULL); \
|
||||
_dynarray_append_with_min_capacity((darr), min_capacity); \
|
||||
}))
|
||||
#else
|
||||
#define dynarray_append_with_min_capacity(darr, min_capacity) \
|
||||
_dynarray_append_with_min_capacity((darr), min_capacity)
|
||||
#endif
|
||||
|
||||
#define dynarray_append(darr) \
|
||||
dynarray_append_with_min_capacity(darr, 2)
|
||||
|
||||
void _dynarray_compact(dynarray_size_t sizeof_element, DynamicArray *darr) attr_nonnull_all;
|
||||
#define dynarray_compact(darr) \
|
||||
_dynarray_dispatch_func_noargs(compact, darr)
|
||||
|
||||
#define dynarray_qsort(darr, compare_func) do { \
|
||||
DynamicArray *_darr = DYNARRAY_CAST_TO_BASE(darr); \
|
||||
if(_darr->data) { \
|
||||
qsort(_darr->data, _darr->num_elements, DYNARRAY_ELEM_SIZE(darr), (compare_func)); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#ifdef USE_GNU_EXTENSIONS
|
||||
#define dynarray_get_ptr(darr, idx) (__extension__ ({ \
|
||||
DYNARRAY_ASSERT_VALID(darr); \
|
||||
__auto_type _darr = NOT_NULL(darr); \
|
||||
dynarray_size_t _darr_idx = (idx); \
|
||||
assume(_darr_idx >= 0); \
|
||||
assume(_darr_idx < _darr->num_elements); \
|
||||
NOT_NULL((darr)->data + _darr_idx); \
|
||||
}))
|
||||
#else
|
||||
#define dynarray_get_ptr(darr, idx) ((darr)->data + (idx))
|
||||
#endif
|
||||
|
||||
#define dynarray_get(darr, idx) (*dynarray_get_ptr(darr, idx))
|
||||
#define dynarray_set(darr, idx, ...) ((*dynarray_get_ptr(darr, idx)) = (__VA_ARGS__))
|
||||
|
||||
void _dynarray_set_elements(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_size_t num_elements, void *elements) attr_nonnull_all;
|
||||
#define dynarray_set_elements(darr, num_elements, elements) do { \
|
||||
DYNARRAY_ASSERT_VALID(darr); \
|
||||
DYNARRAY_CHECK_ELEMENT_TYPE(darr, *(elements)); \
|
||||
_dynarray_dispatch_func(set_elements, darr, num_elements, elements); \
|
||||
} while(0)
|
||||
|
||||
typedef bool (*dynarray_filter_predicate_t)(const void *pelem, void *userdata);
|
||||
void _dynarray_filter(dynarray_size_t sizeof_element, DynamicArray *darr, dynarray_filter_predicate_t predicate, void *userdata) attr_nonnull(2, 3);
|
||||
#define dynarray_filter(darr, predicate, userdata) \
|
||||
_dynarray_dispatch_func(filter, darr, predicate, userdata)
|
||||
|
||||
#ifdef USE_GNU_EXTENSIONS
|
||||
#define dynarray_indexof(darr, pelem) (__extension__ ({ \
|
||||
DYNARRAY_ASSERT_VALID(darr); \
|
||||
__auto_type _darr = NOT_NULL(darr); \
|
||||
__auto_type _darr_pelem = NOT_NULL(pelem); \
|
||||
DYNARRAY_CHECK_ELEMENT_TYPE(_darr, *(_darr_pelem)); \
|
||||
intptr_t _darr_idx = (intptr_t)(_darr_pelem - _darr->data); \
|
||||
assume(_darr_idx >= 0); \
|
||||
assume(_darr_idx < _darr->num_elements); \
|
||||
(dynarray_size_t)_darr_idx; \
|
||||
}))
|
||||
#else
|
||||
#define dynarray_indexof(darr, pelem) ((intptr_t)((pelem) - (darr)->data))
|
||||
#endif
|
||||
|
||||
#define _dynarray_foreach_iter MACROHAX_ADDLINENUM(_dynarray_foreach_iter)
|
||||
#define _dynarray_foreach_ignored MACROHAX_ADDLINENUM(_dynarray_foreach_ignored)
|
||||
|
||||
#define dynarray_foreach(_darr, _cntr_var, _pelem_var, ...) do { \
|
||||
for(dynarray_size_t _dynarray_foreach_iter = 0; \
|
||||
_dynarray_foreach_iter < NOT_NULL(_darr)->num_elements; \
|
||||
++_dynarray_foreach_iter \
|
||||
) { \
|
||||
_cntr_var = _dynarray_foreach_iter; \
|
||||
_pelem_var = dynarray_get_ptr((_darr), _dynarray_foreach_iter); \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define dynarray_foreach_elem(_darr, _pelem_var, ...) \
|
||||
dynarray_foreach(_darr, attr_unused dynarray_size_t _dynarray_foreach_ignored, _pelem_var, __VA_ARGS__)
|
||||
|
||||
#define dynarray_foreach_idx(_darr, _cntr_var, ...) \
|
||||
dynarray_foreach(_darr, _cntr_var, attr_unused void *_dynarray_foreach_ignored, __VA_ARGS__)
|
||||
|
||||
#endif // IGUARD_dynarray_h
|
63
src/ending.c
63
src/ending.c
|
@ -12,16 +12,16 @@
|
|||
#include "global.h"
|
||||
#include "video.h"
|
||||
#include "progress.h"
|
||||
#include "dynarray.h"
|
||||
|
||||
typedef struct EndingEntry {
|
||||
char *msg;
|
||||
const char *msg;
|
||||
Sprite *sprite;
|
||||
int time;
|
||||
} EndingEntry;
|
||||
|
||||
typedef struct Ending {
|
||||
EndingEntry *entries;
|
||||
int count;
|
||||
DYNAMIC_ARRAY(EndingEntry) entries;
|
||||
int duration;
|
||||
int pos;
|
||||
CallChain cc;
|
||||
|
@ -33,18 +33,14 @@ static void track_ending(int ending) {
|
|||
}
|
||||
|
||||
static void add_ending_entry(Ending *e, int dur, const char *msg, const char *sprite) {
|
||||
EndingEntry *entry;
|
||||
e->entries = realloc(e->entries, (++e->count)*sizeof(EndingEntry));
|
||||
entry = &e->entries[e->count-1];
|
||||
|
||||
EndingEntry *entry = dynarray_append(&e->entries);
|
||||
entry->time = 0;
|
||||
|
||||
if(e->count > 1) {
|
||||
if(e->entries.num_elements > 1) {
|
||||
entry->time = 1<<23; // nobody will ever! find out
|
||||
}
|
||||
|
||||
entry->msg = NULL;
|
||||
stralloc(&entry->msg, msg);
|
||||
entry->msg = msg;
|
||||
|
||||
if(sprite) {
|
||||
entry->sprite = get_sprite(sprite);
|
||||
|
@ -330,6 +326,8 @@ void good_ending_reimu(Ending *e) {
|
|||
}
|
||||
|
||||
static void init_ending(Ending *e) {
|
||||
dynarray_ensure_capacity(&e->entries, 32);
|
||||
|
||||
if(global.plr.continues_used) {
|
||||
global.plr.mode->character->ending.bad(e);
|
||||
} else {
|
||||
|
@ -337,22 +335,19 @@ static void init_ending(Ending *e) {
|
|||
add_ending_entry(e, 400, "-UNDER CONSTRUCTION-\nExtra Stage coming soon! Please wait warmly for it!", NULL);
|
||||
}
|
||||
|
||||
add_ending_entry(e, 400, "", NULL); // this is important
|
||||
add_ending_entry(e, 400, "", NULL); // this is important <-- TODO explain *why* this is important?
|
||||
e->duration = 1<<23;
|
||||
}
|
||||
|
||||
static void free_ending(Ending *e) {
|
||||
int i;
|
||||
for(i = 0; i < e->count; i++)
|
||||
free(e->entries[i].msg);
|
||||
free(e->entries);
|
||||
}
|
||||
|
||||
static void ending_draw(Ending *e) {
|
||||
float s, d;
|
||||
|
||||
int t1 = global.frames-e->entries[e->pos].time;
|
||||
int t2 = e->entries[e->pos+1].time-global.frames;
|
||||
EndingEntry *e_this, *e_next;
|
||||
e_this = dynarray_get_ptr(&e->entries, e->pos);
|
||||
e_next = dynarray_get_ptr(&e->entries, e->pos + 1);
|
||||
|
||||
int t1 = global.frames - e_this->time;
|
||||
int t2 = e_next->time - global.frames;
|
||||
|
||||
d = 1.0/ENDING_FADE_TIME;
|
||||
|
||||
|
@ -365,9 +360,9 @@ static void ending_draw(Ending *e) {
|
|||
|
||||
r_color4(s, s, s, s);
|
||||
|
||||
if(e->entries[e->pos].sprite) {
|
||||
if(e_this->sprite) {
|
||||
r_draw_sprite(&(SpriteParams) {
|
||||
.sprite_ptr = e->entries[e->pos].sprite,
|
||||
.sprite_ptr = e_this->sprite,
|
||||
.pos = { SCREEN_W/2, SCREEN_H/2 },
|
||||
.shader = "sprite_default",
|
||||
.color = r_color_current(),
|
||||
|
@ -375,7 +370,7 @@ static void ending_draw(Ending *e) {
|
|||
}
|
||||
|
||||
r_shader("text_default");
|
||||
text_draw_wrapped(e->entries[e->pos].msg, SCREEN_W * 0.85, &(TextParams) {
|
||||
text_draw_wrapped(e_this->msg, SCREEN_W * 0.85, &(TextParams) {
|
||||
.pos = { SCREEN_W/2, VIEWPORT_H*4/5 },
|
||||
.align = ALIGN_CENTER,
|
||||
});
|
||||
|
@ -389,12 +384,17 @@ void ending_preload(void) {
|
|||
}
|
||||
|
||||
static void ending_advance(Ending *e) {
|
||||
EndingEntry *e_this, *e_next;
|
||||
e_this = dynarray_get_ptr(&e->entries, e->pos);
|
||||
e_next = dynarray_get_ptr(&e->entries, e->pos + 1);
|
||||
|
||||
if(
|
||||
e->pos < e->count-1 &&
|
||||
e->entries[e->pos].time + ENDING_FADE_TIME < global.frames &&
|
||||
global.frames < e->entries[e->pos+1].time - ENDING_FADE_TIME
|
||||
e->pos < e->entries.num_elements - 1 &&
|
||||
e_this->time + ENDING_FADE_TIME < global.frames &&
|
||||
global.frames < e_next->time - ENDING_FADE_TIME
|
||||
) {
|
||||
e->entries[e->pos+1].time = global.frames+(e->pos != e->count-2)*ENDING_FADE_TIME;
|
||||
// TODO find out wtf that last part is for
|
||||
e_next->time = global.frames + (e->pos != e->entries.num_elements - 2) * ENDING_FADE_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -427,9 +427,9 @@ static LogicFrameAction ending_logic_frame(void *arg) {
|
|||
|
||||
global.frames++;
|
||||
|
||||
if(e->pos < e->count-1 && global.frames >= e->entries[e->pos+1].time) {
|
||||
if(e->pos < e->entries.num_elements - 1 && global.frames >= dynarray_get(&e->entries, e->pos + 1).time) {
|
||||
e->pos++;
|
||||
if(e->pos == e->count-1) {
|
||||
if(e->pos == e->entries.num_elements - 1) {
|
||||
fade_bgm((FPS * ENDING_FADE_OUT) / 4000.0);
|
||||
set_transition(TransFadeWhite, ENDING_FADE_OUT, ENDING_FADE_OUT);
|
||||
e->duration = global.frames+ENDING_FADE_OUT;
|
||||
|
@ -452,8 +452,9 @@ static LogicFrameAction ending_logic_frame(void *arg) {
|
|||
static RenderFrameAction ending_render_frame(void *arg) {
|
||||
Ending *e = arg;
|
||||
|
||||
if(e->pos < e->count-1)
|
||||
if(e->pos < e->entries.num_elements - 1) {
|
||||
ending_draw(e);
|
||||
}
|
||||
|
||||
draw_transition();
|
||||
return RFRAME_SWAP;
|
||||
|
@ -462,7 +463,7 @@ static RenderFrameAction ending_render_frame(void *arg) {
|
|||
static void ending_loop_end(void *ctx) {
|
||||
Ending *e = ctx;
|
||||
CallChain cc = e->cc;
|
||||
free_ending(e);
|
||||
dynarray_free_data(&e->entries);
|
||||
free(e);
|
||||
progress_unlock_bgm("ending");
|
||||
run_call_chain(&cc, NULL);
|
||||
|
|
61
src/entity.c
61
src/entity.c
|
@ -12,6 +12,7 @@
|
|||
#include "util.h"
|
||||
#include "renderer/api.h"
|
||||
#include "global.h"
|
||||
#include "dynarray.h"
|
||||
|
||||
typedef struct EntityDrawHook EntityDrawHook;
|
||||
typedef LIST_ANCHOR(EntityDrawHook) EntityDrawHookList;
|
||||
|
@ -23,9 +24,7 @@ struct EntityDrawHook {
|
|||
};
|
||||
|
||||
static struct {
|
||||
EntityInterface **array;
|
||||
uint num;
|
||||
uint capacity;
|
||||
DYNAMIC_ARRAY(EntityInterface*) registered;
|
||||
uint32_t total_spawns;
|
||||
|
||||
struct {
|
||||
|
@ -59,20 +58,17 @@ static void call_hooks(EntityDrawHookList *list, EntityInterface *ent) {
|
|||
}
|
||||
}
|
||||
|
||||
#define FOR_EACH_ENT(ent) for(EntityInterface **_ent = entities.array, *ent = *entities.array; _ent < entities.array + entities.num; ent = *(++_ent))
|
||||
|
||||
void ent_init(void) {
|
||||
memset(&entities, 0, sizeof(entities));
|
||||
entities.capacity = 4096;
|
||||
entities.array = calloc(entities.capacity, sizeof(EntityInterface*));
|
||||
dynarray_ensure_capacity(&entities.registered, 1024);
|
||||
}
|
||||
|
||||
void ent_shutdown(void) {
|
||||
if(entities.num) {
|
||||
log_fatal_if_debug("%u entities were not properly unregistered, this is a bug!", entities.num);
|
||||
if(entities.registered.num_elements) {
|
||||
log_fatal_if_debug("%u entities were not properly unregistered, this is a bug!", entities.registered.num_elements);
|
||||
}
|
||||
|
||||
free(entities.array);
|
||||
dynarray_free_data(&entities.registered);
|
||||
|
||||
assert(entities.hooks.post_draw.first == NULL);
|
||||
assert(entities.hooks.pre_draw.first == NULL);
|
||||
|
@ -81,29 +77,22 @@ void ent_shutdown(void) {
|
|||
void ent_register(EntityInterface *ent, EntityType type) {
|
||||
assert(type > _ENT_TYPE_ENUM_BEGIN && type < _ENT_TYPE_ENUM_END);
|
||||
ent->type = type;
|
||||
ent->index = entities.num++;
|
||||
ent->spawn_id = ++entities.total_spawns;
|
||||
|
||||
assert(ent->spawn_id > 0);
|
||||
|
||||
if(entities.capacity < entities.num) {
|
||||
entities.capacity *= 2;
|
||||
entities.array = realloc(entities.array, entities.capacity * sizeof(EntityInterface*));
|
||||
}
|
||||
|
||||
entities.array[ent->index] = ent;
|
||||
|
||||
assert(ent->index < entities.num);
|
||||
assert(entities.array[ent->index] == ent);
|
||||
ent->index = entities.registered.num_elements;
|
||||
assume(ent->spawn_id > 0);
|
||||
*dynarray_append(&entities.registered) = ent;
|
||||
}
|
||||
|
||||
void ent_unregister(EntityInterface *ent) {
|
||||
ent->spawn_id = 0;
|
||||
EntityInterface *sub = entities.array[--entities.num];
|
||||
assert(ent->index <= entities.num);
|
||||
assert(entities.array[ent->index] == ent);
|
||||
|
||||
// Fast non-order-preserving removal by moving the last element into the removed element's position.
|
||||
|
||||
assert(ent->index < entities.registered.num_elements);
|
||||
assert(dynarray_get(&entities.registered, ent->index) == ent);
|
||||
EntityInterface *sub = entities.registered.data[--entities.registered.num_elements];
|
||||
entities.registered.data[sub->index = ent->index] = sub;
|
||||
del_ref(ent);
|
||||
entities.array[sub->index = ent->index] = sub;
|
||||
}
|
||||
|
||||
static int ent_cmp(const void *ptr1, const void *ptr2) {
|
||||
|
@ -126,12 +115,12 @@ static inline bool ent_is_drawable(EntityInterface *ent) {
|
|||
|
||||
void ent_draw(EntityPredicate predicate) {
|
||||
call_hooks(&entities.hooks.pre_draw, NULL);
|
||||
qsort(entities.array, entities.num, sizeof(EntityInterface*), ent_cmp);
|
||||
dynarray_qsort(&entities.registered, ent_cmp);
|
||||
|
||||
if(predicate) {
|
||||
FOR_EACH_ENT(ent) {
|
||||
ent->index = _ent - entities.array;
|
||||
assert(entities.array[ent->index] == ent);
|
||||
dynarray_foreach(&entities.registered, int i, EntityInterface **pent, {
|
||||
EntityInterface *ent = *pent;
|
||||
ent->index = i;
|
||||
|
||||
if(ent_is_drawable(ent) && predicate(ent)) {
|
||||
call_hooks(&entities.hooks.pre_draw, ent);
|
||||
|
@ -140,11 +129,11 @@ void ent_draw(EntityPredicate predicate) {
|
|||
r_state_pop();
|
||||
call_hooks(&entities.hooks.post_draw, ent);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
FOR_EACH_ENT(ent) {
|
||||
ent->index = _ent - entities.array;
|
||||
assert(entities.array[ent->index] == ent);
|
||||
dynarray_foreach(&entities.registered, int i, EntityInterface **pent, {
|
||||
EntityInterface *ent = *pent;
|
||||
ent->index = i;
|
||||
|
||||
if(ent_is_drawable(ent)) {
|
||||
call_hooks(&entities.hooks.pre_draw, ent);
|
||||
|
@ -153,7 +142,7 @@ void ent_draw(EntityPredicate predicate) {
|
|||
r_state_pop();
|
||||
call_hooks(&entities.hooks.post_draw, ent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
call_hooks(&entities.hooks.post_draw, NULL);
|
||||
|
|
|
@ -223,9 +223,9 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
if(ctx->cli.type == CLI_DumpStages) {
|
||||
for(StageInfo *stg = stages; stg->procs; ++stg) {
|
||||
dynarray_foreach_elem(&stages, StageInfo *stg, {
|
||||
tsfprintf(stdout, "%X %s: %s\n", stg->id, stg->title, stg->subtitle);
|
||||
}
|
||||
});
|
||||
|
||||
main_quit(ctx, 0);
|
||||
}
|
||||
|
|
|
@ -83,11 +83,13 @@ static void update_char_draw_order(MenuData *menu) {
|
|||
}
|
||||
|
||||
static void update_char_menu(MenuData *menu) {
|
||||
for(int i = 0; i < menu->ecount; i++) {
|
||||
menu->entries[i].drawdata += 0.05*((menu->cursor != i) - menu->entries[i].drawdata);
|
||||
}
|
||||
dynarray_foreach(&menu->entries, int i, MenuEntry *e, {
|
||||
e->drawdata += 0.05 * ((menu->cursor != i) - e->drawdata);
|
||||
});
|
||||
|
||||
PlayerCharacter *pchar = plrchar_get((CharacterID)(uintptr_t)menu->entries[menu->cursor].arg);
|
||||
MenuEntry *cursor_entry = dynarray_get_ptr(&menu->entries, menu->cursor);
|
||||
|
||||
PlayerCharacter *pchar = plrchar_get((CharacterID)(uintptr_t)cursor_entry->arg);
|
||||
assume(pchar != NULL);
|
||||
|
||||
PlayerMode *m = plrmode_find(pchar->id, SELECTED_SUBSHOT(menu));
|
||||
|
@ -99,7 +101,7 @@ static void update_char_menu(MenuData *menu) {
|
|||
double height = text_height(font, buf, 0) + font_get_lineskip(font) * 2;
|
||||
|
||||
fapproach_asymptotic_p(&menu->drawdata[0], SELECTED_SUBSHOT(menu) - PLR_SHOT_A, 0.1, 1e-5);
|
||||
fapproach_asymptotic_p(&menu->drawdata[1], 1 - menu->entries[menu->cursor].drawdata, 0.1, 1e-5);
|
||||
fapproach_asymptotic_p(&menu->drawdata[1], 1 - cursor_entry->drawdata, 0.1, 1e-5);
|
||||
fapproach_asymptotic_p(&menu->drawdata[2], height, 0.1, 1e-5);
|
||||
}
|
||||
|
||||
|
@ -156,17 +158,18 @@ void draw_char_menu(MenuData *menu) {
|
|||
};
|
||||
|
||||
assert(menu->cursor < 3);
|
||||
PlayerCharacter *selected_char = plrchar_get((CharacterID)(uintptr_t)menu->entries[menu->cursor].arg);
|
||||
PlayerCharacter *selected_char = plrchar_get((CharacterID)(uintptr_t)dynarray_get(&menu->entries, menu->cursor).arg);
|
||||
|
||||
draw_main_menu_bg(menu, SCREEN_W/4+100, 0, 0.1 * (0.5 + 0.5 * menu->drawdata[1]), "menu/mainmenubg", selected_char->menu_texture_name);
|
||||
draw_menu_title(menu, "Select Character");
|
||||
|
||||
CharacterID current_char = 0;
|
||||
|
||||
for(int j = 0; j < menu->ecount; j++) {
|
||||
dynarray_foreach_idx(&menu->entries, int j, {
|
||||
CharacterID i = ctx->char_draw_order[j];
|
||||
MenuEntry *e = dynarray_get_ptr(&menu->entries, i);
|
||||
|
||||
PlayerCharacter *pchar = plrchar_get((CharacterID)(uintptr_t)menu->entries[i].arg);
|
||||
PlayerCharacter *pchar = plrchar_get((CharacterID)(uintptr_t)e->arg);
|
||||
assert(pchar != NULL);
|
||||
assert(pchar->id == i);
|
||||
|
||||
|
@ -178,8 +181,7 @@ void draw_char_menu(MenuData *menu) {
|
|||
current_char = pchar->id;
|
||||
}
|
||||
|
||||
float o = 1-menu->entries[i].drawdata*2;
|
||||
|
||||
float o = 1 - e->drawdata*2;
|
||||
const char *face;
|
||||
|
||||
if(menu->selected == i) {
|
||||
|
@ -192,7 +194,7 @@ void draw_char_menu(MenuData *menu) {
|
|||
face = facedefs[i][F_UNAMUSED];
|
||||
}
|
||||
|
||||
float pofs = max(0, menu->entries[i].drawdata * 1.5 - 0.5);
|
||||
float pofs = max(0, e->drawdata * 1.5 - 0.5);
|
||||
pofs = glm_ease_back_in(pofs);
|
||||
|
||||
if(i != menu->selected) {
|
||||
|
@ -218,9 +220,9 @@ void draw_char_menu(MenuData *menu) {
|
|||
|
||||
r_mat_mv_push();
|
||||
|
||||
if(menu->entries[i].drawdata != 0) {
|
||||
r_mat_mv_translate(0, -300 * menu->entries[i].drawdata, 0);
|
||||
r_mat_mv_rotate(M_PI * menu->entries[i].drawdata, 1, 0, 0);
|
||||
if(e->drawdata != 0) {
|
||||
r_mat_mv_translate(0, -300 * e->drawdata, 0);
|
||||
r_mat_mv_rotate(M_PI * e->drawdata, 1, 0, 0);
|
||||
}
|
||||
|
||||
text_draw(name, &(TextParams) {
|
||||
|
@ -232,8 +234,8 @@ void draw_char_menu(MenuData *menu) {
|
|||
|
||||
r_mat_mv_pop();
|
||||
|
||||
if(menu->entries[i].drawdata) {
|
||||
o = 1-menu->entries[i].drawdata*3;
|
||||
if(e->drawdata) {
|
||||
o = 1 - e->drawdata * 3;
|
||||
} else {
|
||||
o = 1;
|
||||
}
|
||||
|
@ -246,7 +248,7 @@ void draw_char_menu(MenuData *menu) {
|
|||
});
|
||||
|
||||
r_mat_mv_pop();
|
||||
}
|
||||
});
|
||||
|
||||
r_mat_mv_push();
|
||||
r_mat_mv_translate(SCREEN_W/4, SCREEN_H/3, 0);
|
||||
|
@ -303,7 +305,7 @@ void draw_char_menu(MenuData *menu) {
|
|||
r_mat_mv_pop();
|
||||
|
||||
float o = 0.3*sin(menu->frames/20.0)+0.5;
|
||||
o *= 1-menu->entries[menu->cursor].drawdata;
|
||||
o *= 1 - dynarray_get(&menu->entries, menu->cursor).drawdata;
|
||||
r_shader("sprite_default");
|
||||
|
||||
r_draw_sprite(&(SpriteParams) {
|
||||
|
@ -354,11 +356,11 @@ static bool char_menu_input_handler(SDL_Event *event, void *arg) {
|
|||
close_menu(menu);
|
||||
}
|
||||
|
||||
menu->cursor = (menu->cursor % menu->ecount) + menu->ecount * (menu->cursor < 0);
|
||||
menu->cursor = (menu->cursor % menu->entries.num_elements) + menu->entries.num_elements * (menu->cursor < 0);
|
||||
ctx->subshot = (ctx->subshot % NUM_SHOT_MODES_PER_CHARACTER) + NUM_SHOT_MODES_PER_CHARACTER * (ctx->subshot < 0);
|
||||
|
||||
if(menu->cursor != prev_cursor) {
|
||||
if(ctx->prev_selected_char != menu->cursor || menu->entries[menu->cursor].drawdata > 0.95) {
|
||||
if(ctx->prev_selected_char != menu->cursor || dynarray_get(&menu->entries, menu->cursor).drawdata > 0.95) {
|
||||
ctx->prev_selected_char = prev_cursor;
|
||||
update_char_draw_order(menu);
|
||||
}
|
||||
|
|
|
@ -39,8 +39,7 @@ static void start_game_internal(MenuData *menu, StageInfo *info, bool difficulty
|
|||
|
||||
if(info == NULL) {
|
||||
global.is_practice_mode = false;
|
||||
ctx->current_stage = stages;
|
||||
ctx->restart_stage = stages;
|
||||
ctx->current_stage = ctx->restart_stage = dynarray_get_ptr(&stages, 0);
|
||||
} else {
|
||||
global.is_practice_mode = (info->type != STAGE_EXTRA);
|
||||
ctx->current_stage = info;
|
||||
|
@ -214,9 +213,7 @@ void draw_menu_list(MenuData *m, float x, float y, void (*draw)(MenuEntry*, int,
|
|||
draw_menu_selector(m->drawdata[0], m->drawdata[2], m->drawdata[1], 34, m->frames);
|
||||
ShaderProgram *text_shader = r_shader_get("text_default");
|
||||
|
||||
for(int i = 0; i < m->ecount; ++i) {
|
||||
MenuEntry *e = &(m->entries[i]);
|
||||
|
||||
dynarray_foreach(&m->entries, int i, MenuEntry *e, {
|
||||
float p = offset + 20*i;
|
||||
|
||||
if(p < -y-10 || p > SCREEN_H+10)
|
||||
|
@ -236,32 +233,32 @@ void draw_menu_list(MenuData *m, float x, float y, void (*draw)(MenuEntry*, int,
|
|||
|
||||
r_color(&clr);
|
||||
|
||||
if(draw && i < m->ecount-1) {
|
||||
draw(e, i, m->ecount);
|
||||
if(draw && i < m->entries.num_elements-1) {
|
||||
draw(e, i, m->entries.num_elements);
|
||||
} else if(e->name) {
|
||||
text_draw(e->name, &(TextParams) {
|
||||
.pos = { 20 - e->drawdata, 20*i },
|
||||
.shader_ptr = text_shader,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
r_mat_mv_pop();
|
||||
}
|
||||
|
||||
void animate_menu_list_entry(MenuData *m, int i) {
|
||||
MenuEntry *e = &(m->entries[i]);
|
||||
MenuEntry *e = dynarray_get_ptr(&m->entries, i);
|
||||
fapproach_asymptotic_p(&e->drawdata, 10 * (i == m->cursor), 0.2, 1e-4);
|
||||
}
|
||||
|
||||
void animate_menu_list_entries(MenuData *m) {
|
||||
for(int i = 0; i < m->ecount; ++i) {
|
||||
dynarray_foreach_idx(&m->entries, int i, {
|
||||
animate_menu_list_entry(m, i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void animate_menu_list(MenuData *m) {
|
||||
MenuEntry *s = m->entries + m->cursor;
|
||||
MenuEntry *s = dynarray_get_ptr(&m->entries, m->cursor);
|
||||
int w = text_width(get_font("standard"), s->name, 0);
|
||||
|
||||
fapproach_asymptotic_p(&m->drawdata[0], 10 + w * 0.5, 0.1, 1e-5);
|
||||
|
|
|
@ -25,9 +25,9 @@ static void set_difficulty(MenuData *m, void *d) {
|
|||
static void update_difficulty_menu(MenuData *menu) {
|
||||
menu->drawdata[0] += (menu->cursor-menu->drawdata[0])*0.1;
|
||||
|
||||
for(int i = 0; i < menu->ecount; ++i) {
|
||||
menu->entries[i].drawdata += 0.2 * ((i == menu->cursor) - menu->entries[i].drawdata);
|
||||
}
|
||||
dynarray_foreach(&menu->entries, int i, MenuEntry *e, {
|
||||
e->drawdata += 0.2 * ((i == menu->cursor) - e->drawdata);
|
||||
});
|
||||
|
||||
color_approach(&diff_color, difficulty_color(menu->cursor + D_Easy), 0.1);
|
||||
}
|
||||
|
@ -45,14 +45,14 @@ MenuData* create_difficulty_menu(void) {
|
|||
add_menu_entry(m, "You must really\nlove books", set_difficulty, (void *)D_Hard);
|
||||
add_menu_entry(m, "You either die a student,\nor live long enough to\nbecome a professor", set_difficulty, (void *)D_Lunatic);
|
||||
|
||||
for(int i = 0; i < m->ecount; ++i) {
|
||||
Difficulty d = (Difficulty)(uintptr_t)m->entries[i].arg;
|
||||
dynarray_foreach(&m->entries, int i, MenuEntry *e, {
|
||||
Difficulty d = (Difficulty)(uintptr_t)e->arg;
|
||||
|
||||
if(d == progress.game_settings.difficulty) {
|
||||
m->cursor = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return m;
|
||||
}
|
||||
|
@ -80,21 +80,21 @@ void draw_difficulty_menu(MenuData *menu) {
|
|||
|
||||
float amp = menu->cursor/2.;
|
||||
float shake = 0.3*amp*menu->frames;
|
||||
text_draw(menu->entries[menu->cursor].name, &(TextParams) {
|
||||
text_draw(dynarray_get(&menu->entries, menu->cursor).name, &(TextParams) {
|
||||
.pos = { 120+15*menu->drawdata[0]+amp*sin(shake), -12+amp*cos(1.57*shake) },
|
||||
});
|
||||
|
||||
r_shader("sprite_default");
|
||||
|
||||
for(int i = 0; i < menu->ecount; ++i) {
|
||||
float scale = 0.5 + menu->entries[i].drawdata;
|
||||
dynarray_foreach(&menu->entries, int i, MenuEntry *e, {
|
||||
float scale = 0.5 + e->drawdata;
|
||||
r_draw_sprite(&(SpriteParams) {
|
||||
.sprite = difficulty_sprite_name(D_Easy + i),
|
||||
.pos = { 0, 240*tanh(0.7*(i-menu->drawdata[0])) },
|
||||
.pos = { 0, 240 * tanh(0.7 * (i - menu->drawdata[0])) },
|
||||
.color = RGBA(scale, scale, scale, scale),
|
||||
.scale.both = scale,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
r_mat_mv_pop();
|
||||
r_state_pop();
|
||||
|
|
|
@ -31,11 +31,11 @@ void restart_game(MenuData *m, void *arg) {
|
|||
static void ingame_menu_do(MenuData *m, MenuAction action) {
|
||||
m->selected = -1;
|
||||
|
||||
for(int i = 0; i < m->ecount; ++i) {
|
||||
if(m->entries[i].action == action) {
|
||||
dynarray_foreach(&m->entries, int i, MenuEntry *e, {
|
||||
if(e->action == action) {
|
||||
m->selected = i;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assert(m->selected >= 0);
|
||||
close_menu(m);
|
||||
|
@ -170,7 +170,7 @@ void draw_ingame_menu_bg(MenuData *menu, float f) {
|
|||
|
||||
void update_ingame_menu(MenuData *menu) {
|
||||
menu->drawdata[0] += (menu->cursor*35 - menu->drawdata[0])/7.0;
|
||||
menu->drawdata[1] += (text_width(get_font("standard"), menu->entries[menu->cursor].name, 0) - menu->drawdata[1])/10.0;
|
||||
menu->drawdata[1] += (text_width(get_font("standard"), dynarray_get(&menu->entries, menu->cursor).name, 0) - menu->drawdata[1])/10.0;
|
||||
}
|
||||
|
||||
void draw_ingame_menu(MenuData *menu) {
|
||||
|
@ -195,8 +195,8 @@ void draw_ingame_menu(MenuData *menu) {
|
|||
});
|
||||
}
|
||||
|
||||
for(int i = 0; i < menu->ecount; i++) {
|
||||
if(menu->entries[i].action) {
|
||||
dynarray_foreach(&menu->entries, int i, MenuEntry *e, {
|
||||
if(e->action) {
|
||||
float s = 0, t = 0.7;
|
||||
if(i == menu->cursor) {
|
||||
t = 1;
|
||||
|
@ -208,11 +208,11 @@ void draw_ingame_menu(MenuData *menu) {
|
|||
r_color(RGBA_MUL_ALPHA(0.5, 0.5, 0.5, 0.5 * (1-menu_fade(menu))));
|
||||
}
|
||||
|
||||
text_draw(menu->entries[i].name, &(TextParams) {
|
||||
text_draw(e->name, &(TextParams) {
|
||||
.align = ALIGN_CENTER,
|
||||
.pos = { 0, i * 35 },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
r_mat_mv_pop();
|
||||
r_state_pop();
|
||||
|
|
|
@ -32,12 +32,15 @@ void main_menu_update_practice_menus(void) {
|
|||
spell_practice_entry->action = NULL;
|
||||
stage_practice_entry->action = NULL;
|
||||
|
||||
for(StageInfo *stg = stages; stg->procs && (!spell_practice_entry->action || !stage_practice_entry->action); ++stg) {
|
||||
dynarray_foreach_elem(&stages, StageInfo *stg, {
|
||||
if(stg->type == STAGE_SPELL) {
|
||||
StageProgress *p = stage_get_progress_from_info(stg, D_Any, false);
|
||||
|
||||
if(p && p->unlocked) {
|
||||
spell_practice_entry->action = menu_action_enter_spellpractice;
|
||||
if(stage_practice_entry->action) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(stg->type == STAGE_STORY) {
|
||||
for(Difficulty d = D_Easy; d <= D_Lunatic; ++d) {
|
||||
|
@ -45,10 +48,13 @@ void main_menu_update_practice_menus(void) {
|
|||
|
||||
if(p && p->unlocked) {
|
||||
stage_practice_entry->action = menu_action_enter_stagepractice;
|
||||
if(spell_practice_entry->action) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void begin_main_menu(MenuData *m) {
|
||||
|
@ -58,9 +64,9 @@ static void begin_main_menu(MenuData *m) {
|
|||
static void update_main_menu(MenuData *menu) {
|
||||
menu->drawdata[1] += 0.1*(menu->cursor-menu->drawdata[1]);
|
||||
|
||||
for(int i = 0; i < menu->ecount; i++) {
|
||||
menu->entries[i].drawdata += 0.2 * ((i == menu->cursor) - menu->entries[i].drawdata);
|
||||
}
|
||||
dynarray_foreach(&menu->entries, int i, MenuEntry *e, {
|
||||
e->drawdata += 0.2 * ((i == menu->cursor) - e->drawdata);
|
||||
});
|
||||
}
|
||||
|
||||
attr_unused
|
||||
|
@ -71,7 +77,7 @@ static bool main_menu_input_handler(SDL_Event *event, void *arg) {
|
|||
|
||||
if(te == TE_MENU_ABORT) {
|
||||
play_ui_sound("hit");
|
||||
m->cursor = m->ecount - 1;
|
||||
m->cursor = m->entries.num_elements - 1;
|
||||
hrtime_t t = time_get();
|
||||
|
||||
if(t - last_abort_time < HRTIME_RESOLUTION/5 && last_abort_time > 0) {
|
||||
|
@ -101,10 +107,14 @@ MenuData* create_main_menu(void) {
|
|||
m->draw = draw_main_menu;
|
||||
m->logic = update_main_menu;
|
||||
|
||||
ptrdiff_t stage_practice_idx, spell_practice_idx;
|
||||
|
||||
add_menu_entry(m, "Start Story", start_game, NULL);
|
||||
add_menu_entry(m, "Start Extra", NULL, NULL);
|
||||
add_menu_entry(m, "Stage Practice", menu_action_enter_stagepractice, NULL);
|
||||
add_menu_entry(m, "Spell Practice", menu_action_enter_spellpractice, NULL);
|
||||
stage_practice_entry = add_menu_entry(m, "Stage Practice", menu_action_enter_stagepractice, NULL);
|
||||
stage_practice_idx = dynarray_indexof(&m->entries, stage_practice_entry);
|
||||
spell_practice_entry = add_menu_entry(m, "Spell Practice", menu_action_enter_spellpractice, NULL);
|
||||
spell_practice_idx = dynarray_indexof(&m->entries, spell_practice_entry);
|
||||
#ifdef DEBUG
|
||||
add_menu_entry(m, "Select Stage", menu_action_enter_stagemenu, NULL);
|
||||
#endif
|
||||
|
@ -116,8 +126,8 @@ MenuData* create_main_menu(void) {
|
|||
m->input = main_menu_input;
|
||||
#endif
|
||||
|
||||
stage_practice_entry = m->entries + 2;
|
||||
spell_practice_entry = m->entries + 3;
|
||||
stage_practice_entry = dynarray_get_ptr(&m->entries, stage_practice_idx);
|
||||
spell_practice_entry = dynarray_get_ptr(&m->entries, spell_practice_idx);
|
||||
main_menu_update_practice_menus();
|
||||
|
||||
progress_unlock_bgm("menu");
|
||||
|
@ -161,20 +171,19 @@ void draw_main_menu(MenuData *menu) {
|
|||
|
||||
float o = 0.7;
|
||||
|
||||
for(int i = 0; i < menu->ecount; i++) {
|
||||
|
||||
if(menu->entries[i].action == NULL) {
|
||||
dynarray_foreach(&menu->entries, int i, MenuEntry *e, {
|
||||
if(e->action == NULL) {
|
||||
r_color4(0.2 * o, 0.3 * o, 0.5 * o, o);
|
||||
} else {
|
||||
float a = 1 - menu->entries[i].drawdata;
|
||||
float a = 1 - e->drawdata;
|
||||
r_color4(o, min(1, 0.7 + a) * o, min(1, 0.4 + a) * o, o);
|
||||
}
|
||||
|
||||
text_draw(menu->entries[i].name, &(TextParams) {
|
||||
.pos = { 50-15*menu->entries[i].drawdata, 20*(i-menu->drawdata[1]) },
|
||||
text_draw(e->name, &(TextParams) {
|
||||
.pos = { 50 - 15 * e->drawdata, 20 * (i - menu->drawdata[1]) },
|
||||
.font = "standard",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
r_mat_mv_pop();
|
||||
|
||||
|
|
|
@ -13,9 +13,7 @@
|
|||
#include "video.h"
|
||||
|
||||
MenuEntry *add_menu_entry(MenuData *menu, const char *name, MenuAction action, void *arg) {
|
||||
menu->entries = realloc(menu->entries, (++menu->ecount)*sizeof(MenuEntry));
|
||||
MenuEntry *e = menu->entries + menu->ecount - 1;
|
||||
memset(e, 0, sizeof(MenuEntry));
|
||||
MenuEntry *e = dynarray_append(&menu->entries);
|
||||
|
||||
stralloc(&e->name, name);
|
||||
e->action = action;
|
||||
|
@ -26,8 +24,7 @@ MenuEntry *add_menu_entry(MenuData *menu, const char *name, MenuAction action, v
|
|||
}
|
||||
|
||||
void add_menu_separator(MenuData *menu) {
|