stageobjects,objectpool: simplify and reimplement on top of arenas

All pools now allocate from the same arena that is initialized once with
8MB of initial space and never deallocated, only reset between stages.
This commit is contained in:
Andrei Alexeyev 2023-04-03 03:49:39 +02:00
parent 66e5a886f0
commit df6b97caf7
No known key found for this signature in database
GPG key ID: 72D26128040B9690
16 changed files with 147 additions and 345 deletions

View file

@ -186,13 +186,6 @@ option(
description : 'Build the no-op audio backend (silence); you want this on!'
)
option(
'objpools',
type : 'boolean',
value : true,
description : 'Pre-allocate memory for game objects (disable for debugging only)'
)
option(
'use_libcrypto',
type : 'feature',

View file

@ -38,7 +38,7 @@ static void calc_spell_bonus(Attack *a, SpellBonus *bonus);
DECLARE_TASK(boss_particles, { BoxedBoss boss; });
Boss *create_boss(char *name, char *ani, cmplx pos) {
Boss *boss = objpool_acquire(stage_object_pools.bosses);
Boss *boss = objpool_acquire(&stage_object_pools.bosses);
boss->name = strdup(name);
boss->pos = pos;
@ -1364,7 +1364,7 @@ void free_boss(Boss *boss) {
boss_set_portrait(boss, NULL, NULL, NULL);
aniplayer_free(&boss->ani);
mem_free(boss->name);
objpool_release(stage_object_pools.bosses, boss);
objpool_release(&stage_object_pools.bosses, boss);
}
static void boss_schedule_next_attack(Boss *b, Attack *a) {

View file

@ -88,7 +88,7 @@ Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule v
log_fatal("Tried to spawn an enemy while in drawing code");
}
Enemy *e = alist_append(enemies, (Enemy*)objpool_acquire(stage_object_pools.enemies));
Enemy *e = alist_append(enemies, (Enemy*)objpool_acquire(&stage_object_pools.enemies));
e->moving = false;
e->dir = 0;
e->birthtime = global.frames;
@ -167,7 +167,7 @@ static void *_delete_enemy(ListAnchor *enemies, List* enemy, void *arg) {
COEVENT_CANCEL_ARRAY(e->events);
ent_unregister(&e->ent);
objpool_release(stage_object_pools.enemies, alist_unlink(enemies, enemy));
objpool_release(&stage_object_pools.enemies, alist_unlink(enemies, enemy));
return NULL;
}

View file

@ -117,7 +117,7 @@ Item* create_item(cmplx pos, cmplx v, ItemType type) {
type = ITEM_SURGE;
}
Item *i = (Item*)objpool_acquire(stage_object_pools.items);
Item *i = objpool_acquire(&stage_object_pools.items);
alist_append(&global.items, i);
i->pos = pos;
@ -137,7 +137,7 @@ Item* create_item(cmplx pos, cmplx v, ItemType type) {
void delete_item(Item *item) {
ent_unregister(&item->ent);
objpool_release(stage_object_pools.items, alist_unlink(&global.items, item));
objpool_release(&stage_object_pools.items, alist_unlink(&global.items, item));
}
Item *create_clear_item(cmplx pos, uint clear_flags) {

View file

@ -43,7 +43,7 @@ Laser *create_laser(
LaserPosRule prule,
cmplx a0, cmplx a1, cmplx a2, cmplx a3
) {
Laser *l = objpool_acquire(stage_object_pools.lasers);
Laser *l = objpool_acquire(&stage_object_pools.lasers);
alist_push(&global.lasers, l);
l->birthtime = global.frames;
@ -98,7 +98,7 @@ void laserline_set_posdir(Laser *l, cmplx pos, cmplx dir) {
static void *_delete_laser(ListAnchor *lasers, List *laser, void *arg) {
Laser *l = (Laser*)laser;
ent_unregister(&l->ent);
objpool_release(stage_object_pools.lasers, alist_unlink(lasers, laser));
objpool_release(&stage_object_pools.lasers, alist_unlink(lasers, laser));
return NULL;
}

View file

@ -44,6 +44,7 @@ static void taisei_shutdown(void) {
progress_unload();
stage_objpools_shutdown();
gamemode_shutdown();
shutdown_resources();
taskmgr_global_shutdown();

View file

@ -78,6 +78,7 @@ taisei_src = files(
'log.c',
'main.c',
'move.c',
'objectpool.c',
'player.c',
'plrmodes.c',
'portrait.c',
@ -106,16 +107,6 @@ if is_developer_build
)
endif
if get_option('objpools')
taisei_src += files(
'objectpool.c',
)
else
taisei_src += files(
'objectpool_fake.c',
)
endif
if host_machine.system() == 'nx'
taisei_src += files(
'arch_switch.c',

View file

@ -11,204 +11,48 @@
#include "objectpool.h"
#include "util.h"
#include "list.h"
#include "memory/arena.h"
typedef struct ObjHeader {
alignas(alignof(max_align_t)) struct ObjHeader *next;
} ObjHeader;
struct ObjectPool {
char *tag;
size_t size_of_object;
size_t max_objects;
#ifdef OBJPOOL_TRACK_STATS
size_t usage;
size_t peak_usage;
#endif
size_t num_extents;
char **extents;
ObjHeader *free_objects;
char objects[];
};
INLINE attr_returns_max_aligned
ObjHeader *obj_ptr(ObjectPool *pool, char *objects, size_t idx) {
return CASTPTR_ASSUME_ALIGNED(objects + idx * pool->size_of_object, ObjHeader);
}
static void objpool_register_objects(ObjectPool *pool, char *objects) {
for(size_t i = 0; i < pool->max_objects; ++i) {
ObjHeader *o = obj_ptr(pool, objects, i);
o->next = pool->free_objects;
pool->free_objects = o;
}
}
ObjectPool *objpool_alloc(size_t obj_size, size_t max_objects, const char *tag) {
// TODO: overflow handling
auto pool = ALLOC_FLEX(ObjectPool, obj_size * max_objects);
pool->size_of_object = obj_size;
pool->max_objects = max_objects;
pool->tag = strdup(tag);
objpool_register_objects(pool, pool->objects);
log_debug("[%s] Allocated pool for %zu objects, %zu bytes each",
pool->tag,
pool->max_objects,
pool->size_of_object
);
return pool;
}
static char *objpool_add_extent(ObjectPool *pool) {
pool->extents = mem_realloc(pool->extents, (++pool->num_extents) * sizeof(*pool->extents));
char *extent = pool->extents[pool->num_extents - 1] = mem_alloc_array(pool->max_objects, pool->size_of_object);
objpool_register_objects(pool, extent);
return extent;
}
static char* objpool_fmt_size(ObjectPool *pool) {
switch(pool->num_extents) {
case 0:
return strfmt("%zu objects, %zu bytes each",
pool->max_objects,
pool->size_of_object
);
case 1:
return strfmt("%zu objects, %zu bytes each, with 1 extent",
pool->max_objects * 2,
pool->size_of_object
);
default:
return strfmt("%zu objects, %zu bytes each, with %zu extents",
pool->max_objects * (1 + pool->num_extents),
pool->size_of_object,
pool->num_extents
);
}
void objpool_init(
ObjectPool *pool,
const char *tag,
MemArena *arena,
size_t obj_size,
size_t obj_align
) {
*pool = (ObjectPool) {
.arena = arena,
.obj_size = obj_size,
.obj_align = obj_align,
.tag = tag,
};
}
void *objpool_acquire(ObjectPool *pool) {
ObjHeader *obj = pool->free_objects;
if(obj) {
acquired:
assert(pool->num_used < pool->num_allocated);
pool->free_objects = obj->next;
memset(obj, 0, pool->size_of_object);
#ifdef OBJPOOL_TRACK_STATS
if(++pool->usage > pool->peak_usage) {
pool->peak_usage = pool->usage;
}
#endif
return obj;
} else {
assert(pool->num_used == pool->num_allocated);
obj = marena_alloc_aligned(pool->arena, pool->obj_size, pool->obj_align);
++pool->num_allocated;
}
char *tmp = objpool_fmt_size(pool);
log_debug("[%s] Object pool exhausted (%s), extending",
pool->tag,
tmp
);
mem_free(tmp);
memset(obj, 0, pool->obj_size);
++pool->num_used;
objpool_add_extent(pool);
obj = pool->free_objects;
assert(obj != NULL);
goto acquired;
assert(pool->num_used <= pool->num_allocated);
return obj;
}
void objpool_release(ObjectPool *pool, void *object) {
objpool_memtest(pool, object);
ObjHeader *obj = object;
obj->next = pool->free_objects;
pool->free_objects = obj;
#ifdef OBJPOOL_TRACK_STATS
pool->usage--;
#endif
--pool->num_used;
assert(pool->num_used >= 0);
assert(pool->num_used <= pool->num_allocated);
}
void objpool_free(ObjectPool *pool) {
#ifdef OBJPOOL_TRACK_STATS
if(pool->usage != 0) {
log_warn("[%s] %zu objects still in use", pool->tag, pool->usage);
}
#endif
for(size_t i = 0; i < pool->num_extents; ++i) {
mem_free(pool->extents[i]);
}
mem_free(pool->extents);
mem_free(pool->tag);
mem_free(pool);
}
size_t objpool_object_size(ObjectPool *pool) {
return pool->size_of_object;
}
void objpool_get_stats(ObjectPool *pool, ObjectPoolStats *stats) {
stats->tag = pool->tag;
stats->capacity = pool->max_objects * (1 + pool->num_extents);
#ifdef OBJPOOL_TRACK_STATS
stats->usage = pool->usage;
stats->peak_usage = pool->peak_usage;
#else
stats->usage = 0;
stats->peak_usage = 0;
#endif
}
attr_unused
static bool objpool_object_in_subpool(ObjectPool *pool, ObjHeader *object, char *objects) {
char *objofs = (char*)object;
char *minofs = objects;
char *maxofs = objects + (pool->max_objects - 1) * pool->size_of_object;
if(objofs < minofs || objofs > maxofs) {
return false;
}
ptrdiff_t misalign = (ptrdiff_t)(objofs - objects) % pool->size_of_object;
if(misalign) {
log_fatal("[%s] Object pointer %p is misaligned by %zi",
pool->tag,
(void*)objofs,
(ssize_t)misalign
);
}
return true;
}
attr_unused
static bool objpool_object_in_pool(ObjectPool *pool, ObjHeader *object) {
if(objpool_object_in_subpool(pool, object, pool->objects)) {
return true;
}
for(size_t i = 0; i < pool->num_extents; ++i) {
if(objpool_object_in_subpool(pool, object, pool->extents[i])) {
return true;
}
}
return false;
}
#ifdef OBJPOOL_DEBUG
void objpool_memtest(ObjectPool *pool, void *object) {
if(!objpool_object_in_pool(pool, object)) {
log_fatal("[%s] Object pointer %p does not belong to this pool",
pool->tag,
object
);
}
}
#endif

View file

@ -10,40 +10,35 @@
#include "taisei.h"
#include "list.h"
#ifdef DEBUG
#define OBJPOOL_DEBUG
#endif
#ifdef OBJPOOL_DEBUG
#define OBJPOOL_TRACK_STATS
#define IF_OBJPOOL_DEBUG(code) code
#else
#define IF_OBJPOOL_DEBUG(code)
#endif
#include "memory/arena.h"
typedef struct ObjectPool ObjectPool;
typedef struct ObjectPoolStats ObjectPoolStats;
struct ObjectPoolStats {
typedef struct ObjHeader {
alignas(alignof(max_align_t)) struct ObjHeader *next;
} ObjHeader;
struct ObjectPool {
MemArena *arena;
ObjHeader *free_objects;
const char *tag;
size_t capacity;
size_t usage;
size_t peak_usage;
size_t obj_size;
size_t obj_align;
int num_allocated;
int num_used;
};
#define OBJPOOL_ALLOC(typename,max_objects) objpool_alloc(sizeof(typename), max_objects, #typename)
#define OBJPOOL_ACQUIRE(pool, type) CASTPTR_ASSUME_ALIGNED(objpool_acquire(pool), type)
void objpool_init(
ObjectPool *pool,
const char *tag,
MemArena *arena,
size_t obj_size,
size_t obj_align
) attr_nonnull(1, 2, 3);
ObjectPool *objpool_alloc(size_t obj_size, size_t max_objects, const char *tag) attr_returns_allocated attr_nonnull(3);
void objpool_free(ObjectPool *pool) attr_nonnull(1);
void *objpool_acquire(ObjectPool *pool) attr_returns_allocated attr_hot attr_nonnull(1);
void objpool_release(ObjectPool *pool, void *object) attr_hot attr_nonnull(1, 2);
void objpool_get_stats(ObjectPool *pool, ObjectPoolStats *stats) attr_nonnull(1, 2);
size_t objpool_object_size(ObjectPool *pool) attr_nonnull(1);
void *objpool_acquire(ObjectPool *pool)
attr_returns_allocated attr_hot attr_nonnull(1);
#ifdef OBJPOOL_DEBUG
void objpool_memtest(ObjectPool *pool, void *object) attr_nonnull(1, 2);
#else
#define objpool_memtest(pool, object) ((void)(pool), (void)(object))
#endif
void objpool_release(ObjectPool *pool, void *object)
attr_hot attr_nonnull(1, 2);

View file

@ -1,46 +0,0 @@
/*
* 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 "objectpool.h"
#include "util.h"
struct ObjectPool {
size_t size_of_object;
};
ObjectPool *objpool_alloc(size_t obj_size, size_t max_objects, const char *tag) {
return ALLOC(ObjectPool, { .size_of_object = obj_size });
}
void *objpool_acquire(ObjectPool *pool) {
return mem_alloc(pool->size_of_object);
}
void objpool_release(ObjectPool *pool, void *object) {
mem_free(object);
}
void objpool_free(ObjectPool *pool) {
mem_free(pool);
}
void objpool_get_stats(ObjectPool *pool, ObjectPoolStats *stats) {
memset(stats, 0, sizeof(ObjectPoolStats));
stats->tag = "<N/A>";
}
size_t objpool_object_size(ObjectPool *pool) {
return pool->size_of_object;
}
#ifdef OBJPOOL_DEBUG
void objpool_memtest(ObjectPool *pool, void *object) {
}
#endif

View file

@ -234,7 +234,7 @@ static Projectile* _create_projectile(ProjArgs *args) {
log_fatal("Tried to spawn a projectile while in drawing code");
}
Projectile *p = (Projectile*)objpool_acquire(stage_object_pools.projectiles);
Projectile *p = objpool_acquire(&stage_object_pools.projectiles);
p->birthtime = global.frames;
p->pos = p->pos0 = p->prevpos = args->pos;
@ -313,7 +313,7 @@ static void delete_projectile(ProjectileList *projlist, Projectile *p, ProjColli
signal_event_with_collision_result(p, &p->events.killed, col);
COEVENT_CANCEL_ARRAY(p->events);
ent_unregister(&p->ent);
objpool_release(stage_object_pools.projectiles, alist_unlink(projlist, p));
objpool_release(&stage_object_pools.projectiles, alist_unlink(projlist, p));
}
static void *foreach_delete_projectile(ListAnchor *projlist, List *proj, void *arg) {

View file

@ -1050,7 +1050,7 @@ static void _stage_enter(
global.stage = stage;
ent_init();
stage_objpools_alloc();
stage_objpools_init();
stage_draw_pre_init();
stage_preload();
stage_draw_init();
@ -1172,7 +1172,6 @@ void stage_end_loop(void *ctx) {
player_free(&global.plr);
ent_shutdown();
rng_make_active(&global.rand_visual);
stage_objpools_free();
stop_all_sfx();
taisei_commit_persistent_data();

View file

@ -1174,22 +1174,43 @@ static void stage_draw_hud_scores(float ypos_hiscore, float ypos_score, char *bu
}
static void stage_draw_hud_objpool_stats(float x, float y, float width) {
ObjectPool **last = &stage_object_pools.first + (sizeof(StageObjectPools)/sizeof(ObjectPool*) - 1);
char buf[128];
auto objpools = STAGE_OBJPOOLS_AS_ARRAYPTR;
MemArena *arena = (*objpools)[0].arena;
Font *font = res_font("monotiny");
ShaderProgram *sh_prev = r_shader_current();
r_shader("text_default");
for(ObjectPool **pool = &stage_object_pools.first; pool <= last; ++pool) {
ObjectPoolStats stats;
char buf[32];
objpool_get_stats(*pool, &stats);
r_shader("text_hud");
snprintf(buf, sizeof(buf), "%zu | %5zu", stats.usage, stats.peak_usage);
// draw_text(ALIGN_LEFT | AL_Flag_NoAdjust, (int)x, (int)y, stats.tag, font);
// draw_text(ALIGN_RIGHT | AL_Flag_NoAdjust, (int)(x + width), (int)y, buf, font);
// y += stringheight(buf, font) * 1.1;
float lineskip = font_get_lineskip(font);
text_draw(stats.tag, &(TextParams) {
text_draw("Arena memory:", &(TextParams) {
.pos = { x, y },
.font_ptr = font,
.align = ALIGN_LEFT,
});
snprintf(buf, sizeof(buf),
"%zukb | %5zukb",
arena->total_used / 1024,
arena->total_allocated / 1024
);
text_draw(buf, &(TextParams) {
.pos = { x + width, y },
.font_ptr = font,
.align = ALIGN_RIGHT,
});
y += lineskip * 1.5;
for(int i = 0; i < ARRAY_SIZE(*objpools); ++i) {
ObjectPool *p = &(*objpools)[i];
snprintf(buf, sizeof(buf), "%u | %7u", p->num_used, p->num_allocated);
text_draw(p->tag, &(TextParams) {
.pos = { x, y },
.font_ptr = font,
.align = ALIGN_LEFT,
@ -1201,8 +1222,9 @@ static void stage_draw_hud_objpool_stats(float x, float y, float width) {
.align = ALIGN_RIGHT,
});
y += font_get_lineskip(font);
y += lineskip;
}
r_shader_ptr(sh_prev);
}
@ -1273,10 +1295,6 @@ static void stage_draw_hud_text(struct labels_s* labels) {
draw_label("Graze:", labels->y.graze, labels, &stagedraw.hud_text.color.label_graze);
r_mat_mv_pop();
if(stagedraw.objpool_stats) {
stage_draw_hud_objpool_stats(0, 390, HUD_EFFECTIVE_WIDTH);
}
// Score/Hi-Score values
stage_draw_hud_scores(labels->y.hiscore, labels->y.score, buf, sizeof(buf));
@ -1443,6 +1461,10 @@ static void stage_draw_hud_text(struct labels_s* labels) {
.color = RGB(1.0, 0.5, 0.2),
});
}
if(stagedraw.objpool_stats) {
stage_draw_hud_objpool_stats(0, 440, HUD_EFFECTIVE_WIDTH);
}
}
void stage_draw_bottom_text(void) {

View file

@ -17,37 +17,33 @@
#include "boss.h"
#include "aniplayer.h"
#define MAX_projectiles 2048
#define MAX_items MAX_projectiles
#define MAX_enemies 64
#define MAX_lasers 64
#define MAX_stagetext 1024
#define MAX_bosses 1
#define OBJECT_POOLS \
OBJECT_POOL(Projectile, projectiles) \
OBJECT_POOL(Item, items) \
OBJECT_POOL(Enemy, enemies) \
OBJECT_POOL(Laser, lasers) \
OBJECT_POOL(StageText, stagetext) \
OBJECT_POOL(Boss, bosses) \
#define INIT_ARENA_SIZE (8 << 20)
StageObjectPools stage_object_pools;
void stage_objpools_alloc(void) {
stage_object_pools = (StageObjectPools){
#define OBJECT_POOL(type,field) \
.field = OBJPOOL_ALLOC(type, MAX_##field),
static struct {
MemArena arena;
} stgobjs;
void stage_objpools_init(void) {
if(!stgobjs.arena.pages.first) {
marena_init(&stgobjs.arena, INIT_ARENA_SIZE - sizeof(MemArenaPage));
} else {
marena_reset(&stgobjs.arena);
}
#define OBJECT_POOL(type, field) \
objpool_init( \
&stage_object_pools.field, \
#type, \
&stgobjs.arena, \
sizeof(type), \
alignof(type));
OBJECT_POOLS
#undef OBJECT_POOL
};
}
void stage_objpools_free(void) {
#define OBJECT_POOL(type,field) \
objpool_free(stage_object_pools.field);
OBJECT_POOLS
#undef OBJECT_POOL
}
void stage_objpools_shutdown(void) {
marena_deinit(&stgobjs.arena);
}

View file

@ -11,22 +11,29 @@
#include "objectpool.h"
typedef struct StageObjectPools {
union {
struct {
ObjectPool *projectiles; // includes particles as well
ObjectPool *items;
ObjectPool *enemies;
ObjectPool *lasers;
ObjectPool *stagetext;
ObjectPool *bosses;
};
#define OBJECT_POOLS \
OBJECT_POOL(Projectile, projectiles) \
OBJECT_POOL(Item, items) \
OBJECT_POOL(Enemy, enemies) \
OBJECT_POOL(Laser, lasers) \
OBJECT_POOL(StageText, stagetext) \
OBJECT_POOL(Boss, bosses) \
ObjectPool *first;
};
typedef struct StageObjectPools {
#define OBJECT_POOL(type, field) \
ObjectPool field;
OBJECT_POOLS
#undef OBJECT_POOL
} StageObjectPools;
extern StageObjectPools stage_object_pools;
void stage_objpools_alloc(void);
void stage_objpools_free(void);
#define STAGE_OBJPOOLS_AS_ARRAYPTR \
(ObjectPool (*)[sizeof(stage_object_pools) / sizeof(ObjectPool)])&stage_object_pools
// Can be called many times to reinitialize the pools while reusing allocated arena memory.
void stage_objpools_init(void);
// Frees the arena
void stage_objpools_shutdown(void);

View file

@ -15,7 +15,7 @@
static StageText *textlist = NULL;
StageText* stagetext_add(const char *text, cmplx pos, Alignment align, Font *font, const Color *clr, int delay, int lifetime, int fadeintime, int fadeouttime) {
StageText *t = (StageText*)objpool_acquire(stage_object_pools.stagetext);
StageText *t = objpool_acquire(&stage_object_pools.stagetext);
list_append(&textlist, t);
if(text != NULL) {
@ -48,7 +48,7 @@ StageText* stagetext_add_numeric(int n, cmplx pos, Alignment align, Font *font,
}
static void* stagetext_delete(List **dest, List *txt, void *arg) {
objpool_release(stage_object_pools.stagetext, list_unlink(dest, txt));
objpool_release(&stage_object_pools.stagetext, list_unlink(dest, txt));
return NULL;
}