object pools

This commit is contained in:
Andrei Alexeyev 2017-12-13 21:05:12 +02:00
parent ca98b39216
commit abbc62b49c
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
23 changed files with 429 additions and 35 deletions

View file

@ -52,6 +52,7 @@ set(SRCs
stage.c
stagedraw.c
stagetext.c
stageobjects.c
replay.c
global.c
events.c
@ -64,6 +65,9 @@ set(SRCs
list.c
refs.c
hashtable.c
objectpool.c
# objectpool_fake.c
objectpool_util.c
boss.c
plrmodes.c
plrmodes/marisa.c

View file

@ -9,6 +9,7 @@
#include "aniplayer.h"
#include "list.h"
#include "global.h"
#include "stageobjects.h"
void aniplayer_create(AniPlayer *plr, Animation *ani) {
memset(plr,0,sizeof(AniPlayer));
@ -19,11 +20,20 @@ AniPlayer* aniplayer_create_copy(AniPlayer *src) {
// XXX: maybe it needs another name since it allocates memory?
// or maybe aniplayer_create needs another name since it doesn't?
AniPlayer *plr = malloc(sizeof(AniPlayer));
// AniPlayer *plr = malloc(sizeof(AniPlayer));
AniPlayer *plr = (AniPlayer*)objpool_acquire(stage_object_pools.aniplayers);
aniplayer_copy(plr, src);
return plr;
}
void aniplayer_free_copy(AniPlayer *ani) {
if(ani) {
aniplayer_free(ani);
objpool_release(stage_object_pools.aniplayers, (ObjectInterface*)ani);
}
}
void aniplayer_free(AniPlayer *plr) {
plr->queuesize = 0; // prevent aniplayer_reset from messing with the queue, since we're going to wipe all of it anyway
list_free_all((List**)&plr->queue);

View file

@ -14,6 +14,7 @@
*/
#include "resource/animation.h"
#include "stageobjects.h"
typedef struct AniSequence AniSequence;
struct AniSequence{
@ -31,6 +32,8 @@ struct AniSequence{
};
typedef struct {
ObjectInterface object_interface; // hack for the boss glow effect
Animation *ani;
int clock;
@ -42,11 +45,13 @@ typedef struct {
} AniPlayer;
void aniplayer_create(AniPlayer *plr, Animation *ani);
AniPlayer* aniplayer_create_copy(AniPlayer *src);
void aniplayer_free(AniPlayer *plr);
void aniplayer_reset(AniPlayer *plr); // resets to a neutral state with empty queue.
void aniplayer_copy(AniPlayer *dst, AniPlayer *src);
AniPlayer* aniplayer_create_copy(AniPlayer *src);
void aniplayer_free_copy(AniPlayer *ani);
AniSequence *aniplayer_queue(AniPlayer *plr, int row, int loops, int delay); // 0 loops: played one time
AniSequence *aniplayer_queue_pro(AniPlayer *plr, int row, int start, int duration, int delay, int speed); // self-documenting pro version
void aniplayer_update(AniPlayer *plr); // makes the inner clocks tick

View file

@ -171,7 +171,7 @@ static void BossGlow(Projectile *p, int t) {
static int boss_glow_rule(Projectile *p, int t) {
if(t == EVENT_DEATH) {
AniPlayer *aplr = REF(p->args[2]);
free(aplr);
aniplayer_free_copy(aplr);
free_ref(p->args[2]);
return 1;
}

View file

@ -97,11 +97,13 @@ typedef struct Attack {
} Attack;
typedef struct Boss {
// XXX: temporary hack for binary compatibility of pos with Enemy and Projectile structs
ObjectInterface object_interface;
complex pos;
Attack *attacks;
Attack *current;
complex pos;
char *name;
int acount;

View file

@ -13,6 +13,7 @@
#include "projectile.h"
#include "list.h"
#include "aniplayer.h"
#include "stageobjects.h"
#ifdef create_enemy_p
#undef create_enemy_p
@ -33,7 +34,7 @@ Enemy *create_enemy_p(Enemy **enemies, complex pos, int hp, EnemyVisualRule visu
}
// XXX: some code relies on the insertion logic
Enemy *e = (Enemy*)list_insert((List**)enemies, malloc(sizeof(Enemy)));
Enemy *e = (Enemy*)list_insert((List**)enemies, (List*)objpool_acquire(stage_object_pools.enemies));
e->moving = false;
e->dir = 0;
@ -77,8 +78,8 @@ void* _delete_enemy(List **enemies, List* enemy, void *arg) {
e->logic_rule(e, EVENT_DEATH);
del_ref(enemy);
objpool_release(stage_object_pools.enemies, (ObjectInterface*)list_unlink(enemies, enemy));
free(list_unlink(enemies, enemy));
return NULL;
}

View file

@ -10,6 +10,7 @@
#include "util.h"
#include "projectile.h"
#include "objectpool.h"
#ifdef DEBUG
#define ENEMY_DEBUG
@ -25,8 +26,13 @@ enum {
};
struct Enemy {
Enemy *next;
Enemy *prev;
union {
ObjectInterface object_interface;
struct {
Enemy *next;
Enemy *prev;
};
};
complex pos;
complex pos0;

View file

@ -9,6 +9,8 @@
#include "item.h"
#include "global.h"
#include "list.h"
#include "stageobjects.h"
#include "objectpool_util.h"
static Texture* item_tex(ItemType type) {
static const char *const map[] = {
@ -31,6 +33,10 @@ static int item_prio(List *litem) {
return item->type;
}
static void* alloc_item(void) {
return objpool_acquire(stage_object_pools.items);
}
Item* create_item(complex pos, complex v, ItemType type) {
if((creal(pos) < 0 || creal(pos) > VIEWPORT_W)) {
// we need this because we clamp the item position to the viewport boundary during motion
@ -38,7 +44,7 @@ Item* create_item(complex pos, complex v, ItemType type) {
return NULL;
}
Item *i = (Item*)list_insert_at_priority((List**)&global.items, malloc(sizeof(Item)), type, item_prio);
Item *i = (Item*)list_insert_at_priority((List**)&global.items, alloc_item(), type, item_prio);
i->pos = pos;
i->pos0 = pos;
i->v = v;
@ -50,7 +56,7 @@ Item* create_item(complex pos, complex v, ItemType type) {
}
void delete_item(Item *item) {
free(list_unlink((List**)&global.items, (List*)item));
objpool_release(stage_object_pools.items, (ObjectInterface*)list_unlink((List**)&global.items, (List*)item));
}
Item* create_bpoint(complex pos) {
@ -89,7 +95,7 @@ void draw_items(void) {
}
void delete_items(void) {
list_free_all((List**)&global.items);
objpool_release_list(stage_object_pools.items, (List**)&global.items);
}
void move_item(Item *i) {

View file

@ -9,9 +9,10 @@
#include "laser.h"
#include "global.h"
#include "list.h"
#include "stageobjects.h"
Laser *create_laser(complex pos, float time, float deathtime, Color color, LaserPosRule prule, LaserLogicRule lrule, complex a0, complex a1, complex a2, complex a3) {
Laser *l = (Laser*)list_push((List**)&global.lasers, malloc(sizeof(Laser)));
Laser *l = (Laser*)list_push((List**)&global.lasers, (List*)objpool_acquire(stage_object_pools.lasers));
l->birthtime = global.frames;
l->timespan = time;
@ -173,7 +174,7 @@ void* _delete_laser(List **lasers, List *laser, void *arg) {
l->lrule(l, EVENT_DEATH);
del_ref(laser);
free(list_unlink(lasers, laser));
objpool_release(stage_object_pools.lasers, (ObjectInterface*)list_unlink(lasers, laser));
return NULL;
}

View file

@ -128,6 +128,10 @@ List* list_unlink(List **dest, List *elem) {
}
List* list_pop(List **dest) {
if(*dest == NULL) {
return NULL;
}
return list_unlink(dest, *dest);
}

146
src/objectpool.c Normal file
View file

@ -0,0 +1,146 @@
#include "objectpool.h"
#include "util.h"
#include "list.h"
#ifdef OBJPOOL_DEBUG
#define OBJ_USED(pool,obj) ((pool)->usemap[((uintptr_t)(obj) - (uintptr_t)(pool)->objects) / (pool)->size_of_object])
#endif
struct ObjectPool {
char *tag;
size_t size_of_object;
size_t max_objects;
size_t usage;
size_t peak_usage;
ObjectInterface *free_objects;
IF_OBJPOOL_DEBUG(
bool *usemap;
)
char objects[];
};
static inline ObjectInterface* obj_ptr(ObjectPool *pool, size_t idx) {
return (ObjectInterface*)(pool->objects + idx * pool->size_of_object);
}
ObjectPool *objpool_alloc(size_t obj_size, size_t max_objects, const char *tag) {
// TODO: overflow handling
ObjectPool *pool = malloc(sizeof(ObjectPool) + (obj_size * max_objects));
pool->size_of_object = obj_size;
pool->max_objects = max_objects;
pool->free_objects = NULL;
pool->usage = 0;
pool->peak_usage = 0;
pool->tag = strdup(tag);
IF_OBJPOOL_DEBUG({
pool->usemap = calloc(max_objects, sizeof(bool));
})
memset(pool->objects, 0, obj_size * max_objects);
for(size_t i = 0; i < max_objects; ++i) {
list_push((List**)&pool->free_objects, (List*)obj_ptr(pool, i));
}
log_debug("[%s] Allocated pool for %zu objects, %zu bytes each",
pool->tag,
pool->max_objects,
pool->size_of_object
);
return pool;
}
ObjectInterface *objpool_acquire(ObjectPool *pool) {
ObjectInterface *obj = (ObjectInterface*)list_pop((List**)&pool->free_objects);
if(obj) {
memset(obj, 0, pool->size_of_object);
IF_OBJPOOL_DEBUG({
OBJ_USED(pool, obj) = true;
})
if(++pool->usage > pool->peak_usage) {
pool->peak_usage = pool->usage;
}
// log_debug("[%s] Usage: %zu", pool->tag, pool->usage);
return obj;
}
log_fatal("[%s] Object pool exhausted (%zu objects, %zu bytes each)",
pool->tag,
pool->max_objects,
pool->size_of_object
);
}
void objpool_release(ObjectPool *pool, ObjectInterface *object) {
IF_OBJPOOL_DEBUG({
char *objofs = (char*)object;
char *minofs = pool->objects;
char *maxofs = pool->objects + (pool->max_objects - 1) * pool->size_of_object;
if(objofs < minofs || objofs > maxofs) {
log_fatal("[%s] Object pointer %p is not within range %p - %p",
pool->tag,
(void*)objofs,
(void*)minofs,
(void*)maxofs
);
}
ptrdiff_t misalign = (ptrdiff_t)(objofs - pool->objects) % pool->size_of_object;
if(misalign) {
log_fatal("[%s] Object pointer %p is misaligned by %zi",
pool->tag,
(void*)objofs,
(ssize_t)misalign
);
}
if(!OBJ_USED(pool, object)) {
log_fatal("[%s] Attempted to release an unused object %p",
pool->tag,
(void*)objofs
);
}
OBJ_USED(pool, object) = false;
})
list_push((List**)pool->free_objects, (List*)object);
pool->usage--;
// log_debug("[%s] Usage: %zu", pool->tag, pool->usage);
}
void objpool_free(ObjectPool *pool) {
if(!pool) {
return;
}
if(pool->usage != 0) {
log_warn("[%s] %zu objects still in use", pool->tag, pool->usage);
}
IF_OBJPOOL_DEBUG({
free(pool->usemap);
})
free(pool->tag);
free(pool);
}
void objpool_get_stats(ObjectPool *pool, ObjectPoolStats *stats) {
stats->tag = pool->tag;
stats->capacity = pool->max_objects;
stats->usage = pool->usage;
stats->peak_usage = pool->peak_usage;
}

44
src/objectpool.h Normal file
View file

@ -0,0 +1,44 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include "list.h"
#ifdef DEBUG
#define OBJPOOL_DEBUG
#endif
#ifdef OBJPOOL_DEBUG
#define IF_OBJPOOL_DEBUG(code) code
#else
#define IF_OBJPOOL_DEBUG(code)
#endif
typedef struct ObjectPool ObjectPool;
typedef struct ObjectInterface ObjectInterface;
typedef struct ObjectPoolStats ObjectPoolStats;
struct ObjectPoolStats {
const char *tag;
size_t capacity;
size_t usage;
size_t peak_usage;
};
struct ObjectInterface {
union {
List list_interface;
struct {
ObjectInterface *next;
ObjectInterface *prev;
};
};
};
ObjectPool *objpool_alloc(size_t obj_size, size_t max_objects, const char *tag);
void objpool_free(ObjectPool *pool);
ObjectInterface *objpool_acquire(ObjectPool *pool);
void objpool_release(ObjectPool *pool, ObjectInterface *object);
void objpool_get_stats(ObjectPool *pool, ObjectPoolStats *stats);

30
src/objectpool_fake.c Normal file
View file

@ -0,0 +1,30 @@
#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) {
ObjectPool *pool = malloc(sizeof(ObjectPool));
pool->size_of_object = obj_size;
return pool;
}
ObjectInterface *objpool_acquire(ObjectPool *pool) {
return calloc(1, pool->size_of_object);
}
void objpool_release(ObjectPool *pool, ObjectInterface *object) {
free(object);
}
void objpool_free(ObjectPool *pool) {
free(pool);
}
void objpool_get_stats(ObjectPool *pool, ObjectPoolStats *stats) {
memset(&stats, 0, sizeof(ObjectPoolStats));
stats->tag = "<N/A>";
}

12
src/objectpool_util.c Normal file
View file

@ -0,0 +1,12 @@
#include "objectpool_util.h"
static void* objpool_release_list_callback(List **dest, List *elem, void *vpool) {
list_unlink(dest, elem);
objpool_release((ObjectPool*)vpool, (ObjectInterface*)elem);
return NULL;
}
void objpool_release_list(ObjectPool *pool, List **dest) {
list_foreach(dest, objpool_release_list_callback, pool);
}

6
src/objectpool_util.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "objectpool.h"
void objpool_release_list(ObjectPool *pool, List **dest);

View file

@ -12,6 +12,7 @@
#include "global.h"
#include "list.h"
#include "vbo.h"
#include "stageobjects.h"
static ProjArgs defaults_proj = {
.texture = "proj/",
@ -110,7 +111,7 @@ static Projectile* _create_projectile(ProjArgs *args) {
log_fatal("Tried to spawn a projectile while in drawing code");
}
Projectile *p = calloc(1, sizeof(Projectile));
Projectile *p = (Projectile*)objpool_acquire(stage_object_pools.projectiles);
p->birthtime = global.frames;
p->pos = p->pos0 = args->pos;
@ -160,7 +161,8 @@ static void* _delete_projectile(List **projs, List *proj, void *arg) {
p->rule(p, EVENT_DEATH);
del_ref(proj);
free(list_unlink(projs, proj));
objpool_release(stage_object_pools.projectiles, (ObjectInterface*)list_unlink(projs, proj));
return NULL;
}

View file

@ -12,6 +12,7 @@
#include "resource/texture.h"
#include "color.h"
#include "recolor.h"
#include "objectpool.h"
#ifdef DEBUG
#define PROJ_DEBUG
@ -52,8 +53,14 @@ typedef enum ProjFlags {
} ProjFlags;
struct Projectile {
struct Projectile *next;
struct Projectile *prev;
union {
ObjectInterface object_interface;
struct {
Projectile *next;
Projectile *prev;
};
};
complex pos;
complex pos0;
complex size; // this is currently ignored if tex is not NULL.

View file

@ -134,6 +134,7 @@ void load_fonts(float quality) {
_fonts.hud = load_font("res/fonts/Laconic_Regular.otf", 19);
_fonts.mono = load_font("res/fonts/ShareTechMono-Regular.ttf", 19);
_fonts.monosmall = load_font("res/fonts/ShareTechMono-Regular.ttf", 14);
_fonts.monotiny = load_font("res/fonts/ShareTechMono-Regular.ttf", 10);
}
void reload_fonts(float quality) {
@ -158,26 +159,48 @@ void free_fonts(void) {
static void draw_text_texture(Alignment align, float x, float y, Texture *tex) {
float m = 1.0 / resources.fontren.quality;
bool adjust = !(align & AL_Flag_NoAdjust);
align &= 0xf;
switch(align) {
case AL_Center:
break;
if(adjust) {
switch(align) {
case AL_Center:
break;
// tex->w/2 is integer division and must be done first
case AL_Left:
x += m*(tex->w/2);
break;
case AL_Right:
x -= m*(tex->w/2);
break;
default:
log_fatal("Invalid alignment %x", align);
}
// tex->w/2 is integer division and must be done first
case AL_Left:
x += m*(tex->w/2);
break;
case AL_Right:
x -= m*(tex->w/2);
break;
// if textures are odd pixeled, align them for ideal sharpness.
if(tex->w&1) {
x += 0.5;
}
if(tex->h&1) {
y += 0.5;
}
} else {
switch(align) {
case AL_Center:
break;
case AL_Left:
x += m*(tex->w/2.0);
break;
case AL_Right:
x -= m*(tex->w/2.0);
break;
default:
log_fatal("Invalid alignment %x", align);
}
}
// if textures are odd pixeled, align them for ideal sharpness.
if(tex->w&1)
x += 0.5;
if(tex->h&1)
y += 0.5;
glPushMatrix();
glTranslatef(x, y, 0);
glScalef(m, m, 1);

View file

@ -17,6 +17,9 @@ typedef enum {
AL_Right
} Alignment;
enum {
AL_Flag_NoAdjust = 0x10,
};
// Size of the buffer used by the font renderer at quality == 1.0.
// No text larger than this can be drawn.
@ -63,6 +66,7 @@ struct Fonts {
TTF_Font *hud;
TTF_Font *mono;
TTF_Font *monosmall;
TTF_Font *monotiny;
};
extern struct Fonts _fonts;

View file

@ -22,6 +22,7 @@
#include "log.h"
#include "stagetext.h"
#include "stagedraw.h"
#include "stageobjects.h"
static size_t numstages = 0;
StageInfo *stages = NULL;
@ -624,6 +625,7 @@ void stage_loop(StageInfo *stage) {
// I really want to separate all of the game state from the global struct sometime
global.stage = stage;
stage_objpools_alloc();
stage_preload();
stage_draw_preload();
@ -695,5 +697,6 @@ void stage_loop(StageInfo *stage) {
player_free(&global.plr);
tsrand_switch(&global.rand_visual);
free_all_refs();
stage_objpools_free();
stop_sounds();
}

View file

@ -13,8 +13,10 @@
#ifdef DEBUG
#define GRAPHS_DEFAULT 1
#define OBJPOOLSTATS_DEFAULT 1
#else
#define GRAPHS_DEFAULT 0
#define OBJPOOLSTATS_DEFAULT 0
#endif
static struct {
@ -28,6 +30,7 @@ static struct {
uint32_t u_split;
} hud_text;
bool framerate_graphs;
bool objpool_stats;
} stagedraw;
void stage_draw_preload(void) {
@ -61,6 +64,7 @@ void stage_draw_preload(void) {
glUseProgram(0);
stagedraw.framerate_graphs = getenvint("TAISEI_FRAMERATE_GRAPHS", GRAPHS_DEFAULT);
stagedraw.objpool_stats = getenvint("TAISEI_OBJPOOL_STATS", OBJPOOLSTATS_DEFAULT);
// As an optimization, static HUD text could be pre-rendered here.
// However, it must be re-rendered on a TE_VIDEO_MODE_CHANGED event in that case.
}
@ -487,6 +491,22 @@ static void stage_draw_hud_scores(float ypos_hiscore, float ypos_score, char *bu
glUniform1f(stagedraw.hud_text.u_split, 0.0);
}
static void stage_draw_hud_objpool_stats(float x, float y, float width, TTF_Font *font) {
ObjectPool **last = &stage_object_pools.first + (sizeof(StageObjectPools)/sizeof(ObjectPool*) - 1);
for(ObjectPool **pool = &stage_object_pools.first; pool <= last; ++pool) {
ObjectPoolStats stats;
char buf[32];
objpool_get_stats(*pool, &stats);
snprintf(buf, sizeof(buf), "%zu | %4zu", stats.usage, stats.peak_usage);
draw_text(AL_Left | AL_Flag_NoAdjust, (int)x, (int)y, stats.tag, font);
draw_text(AL_Right | AL_Flag_NoAdjust, (int)(x + width), (int)y, buf, font);
y += stringheight(buf, font) * 1.1;
}
}
struct labels_s {
struct {
float ofs;
@ -520,6 +540,10 @@ void stage_draw_hud_text(struct labels_s* labels) {
draw_text(AL_Left, labels->x.ofs, labels->y.graze, "Graze:", _fonts.hud);
glUniform4f(stagedraw.hud_text.u_colortint, 1.00, 1.00, 1.00, 1.00);
if(stagedraw.objpool_stats) {
stage_draw_hud_objpool_stats(labels->x.ofs, labels->y.graze + 32, 250, _fonts.monotiny);
}
// Score/Hi-Score values
stage_draw_hud_scores(labels->y.hiscore + labels->y.mono_ofs, labels->y.score + labels->y.mono_ofs, buf, sizeof(buf));
@ -549,7 +573,7 @@ void stage_draw_hud_text(struct labels_s* labels) {
snprintf(buf, sizeof(buf), "%.2f fps", global.fps.fps);
#endif
draw_text(AL_Right, SCREEN_W, rint(SCREEN_H - 0.5 * stringheight(buf, _fonts.monosmall)), buf, _fonts.monosmall);
draw_text(AL_Right | AL_Flag_NoAdjust, SCREEN_W, rint(SCREEN_H - 0.5 * stringheight(buf, _fonts.monosmall)), buf, _fonts.monosmall);
if(global.replaymode == REPLAY_PLAY) {
// XXX: does it make sense to use the monospace font here?

31
src/stageobjects.c Normal file
View file

@ -0,0 +1,31 @@
#include "stageobjects.h"
#include "projectile.h"
#include "item.h"
#include "enemy.h"
#include "laser.h"
#include "aniplayer.h"
#define MAX_PROJECTILES 8192
#define MAX_ITEMS MAX_PROJECTILES
#define MAX_ENEMIES 64
#define MAX_LASERS 256
#define MAX_ANIPLAYERS 32
StageObjectPools stage_object_pools;
void stage_objpools_alloc(void) {
stage_object_pools.projectiles = objpool_alloc(sizeof(Projectile), MAX_PROJECTILES, "proj+part");
stage_object_pools.items = objpool_alloc(sizeof(Item), MAX_ITEMS, "item");
stage_object_pools.enemies = objpool_alloc(sizeof(Enemy), MAX_ENEMIES, "enemy");
stage_object_pools.lasers = objpool_alloc(sizeof(Laser), MAX_LASERS, "laser");
stage_object_pools.aniplayers = objpool_alloc(sizeof(AniPlayer), MAX_ANIPLAYERS, "aniplr");
}
void stage_objpools_free(void) {
objpool_free(stage_object_pools.projectiles);
objpool_free(stage_object_pools.items);
objpool_free(stage_object_pools.enemies);
objpool_free(stage_object_pools.lasers);
objpool_free(stage_object_pools.aniplayers);
}

23
src/stageobjects.h Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "objectpool.h"
typedef struct StageObjectPools {
union {
struct {
ObjectPool *projectiles; // includes particles as well
ObjectPool *items;
ObjectPool *enemies;
ObjectPool *lasers;
ObjectPool *aniplayers; // hack for the boss glow effect
};
ObjectPool *first;
};
} StageObjectPools;
extern StageObjectPools stage_object_pools;
void stage_objpools_alloc(void);
void stage_objpools_free(void);