separate entity handling from world to new entity_store

This commit is contained in:
jacob 2024-03-11 20:55:49 -05:00
parent 495d7c97ee
commit efa639c971
7 changed files with 187 additions and 132 deletions

View File

@ -7,3 +7,94 @@ READONLY struct entity _g_entity_nil = {
.sprite_xform = XFORM_IDENT, .sprite_xform = XFORM_IDENT,
.sprite_tint = COLOR_WHITE .sprite_tint = COLOR_WHITE
}; };
/* ========================== *
* Store allocation
* ========================== */
struct entity_store entity_store_alloc(void)
{
struct entity_store store = { 0 };
store.arena = arena_alloc(GIGABYTE(64));
return store;
}
void entity_store_release(struct entity_store *store)
{
arena_release(&store->arena);
}
void entity_store_copy_replace(struct entity_store *dest, struct entity_store *src)
{
struct arena orig_arena = dest->arena;
MEMCPY_STRUCT(dest, src);
dest->arena = orig_arena;
arena_copy_replace(&dest->arena, &src->arena);
}
/* ========================== *
* Allocation
* ========================== */
struct entity *entity_alloc(struct entity_store *store)
{
struct entity *entity = NULL;
if (store->first_free.gen) {
/* Reuse from free list */
entity = entity_from_handle(store, store->first_free);
store->first_free = entity->next_free;
entity->next_free = (struct entity_handle) { 0 };
} else {
/* Make new */
u64 idx = store->count++;
entity = arena_push(&store->arena, struct entity);
*entity = *entity_nil();
entity->handle = (struct entity_handle) { .gen = 1, .idx = idx };
}
return entity;
}
void entity_release(struct entity_store *store, struct entity *entity)
{
struct entity_handle handle = (struct entity_handle) { .gen = entity->handle.gen, .idx = entity->handle.idx };
*entity = *entity_nil();
entity->handle = handle;
entity->next_free = store->first_free;
store->first_free = handle;
}
/* ========================== *
* Lookup
* ========================== */
/* Returns a valid entity or nil entity. Always safe to read result, need to check to write. */
struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle)
{
if (handle.idx < store->count) {
struct entity *entities = (struct entity *)store->arena.base;
struct entity *entity = &entities[handle.idx];
if (entity->handle.gen == handle.gen) {
return entity;
}
}
return entity_nil();
}
void entity_link(struct entity_store *store, struct entity *parent, struct entity *child)
{
struct entity *first_child = entity_from_handle(store, parent->first);
struct entity *last_child = entity_from_handle(store, parent->last);
child->prev = last_child->handle;
child->parent = parent->handle;
if (last_child->valid) {
last_child->next = child->handle;
}
parent->last = child->handle;
if (!first_child->valid) {
parent->first = child->handle;
}
}

View File

@ -4,11 +4,6 @@
#include "sheet.h" #include "sheet.h"
#include "mixer.h" #include "mixer.h"
struct entity_handle {
u64 idx;
u64 gen;
};
enum entity_prop { enum entity_prop {
ENTITY_PROP_NONE, ENTITY_PROP_NONE,
@ -25,6 +20,17 @@ enum entity_prop {
ENTITY_PROP_COUNT ENTITY_PROP_COUNT
}; };
struct entity_handle {
u64 idx;
u64 gen;
};
struct entity_store {
struct arena arena;
u64 count;
struct entity_handle first_free;
};
struct entity { struct entity {
b32 valid; b32 valid;
struct entity_handle handle; struct entity_handle handle;
@ -91,9 +97,22 @@ struct entity {
f32 camera_zoom; f32 camera_zoom;
}; };
struct entity_array {
struct entity *entities;
u64 count;
};
/* ========================== *
* Nil
* ========================== */
extern READONLY struct entity _g_entity_nil; extern READONLY struct entity _g_entity_nil;
INLINE READONLY struct entity *entity_nil(void) { return &_g_entity_nil; } INLINE READONLY struct entity *entity_nil(void) { return &_g_entity_nil; }
/* ========================== *
* Property helpers
* ========================== */
INLINE void entity_enable_prop(struct entity *entity, enum entity_prop prop) INLINE void entity_enable_prop(struct entity *entity, enum entity_prop prop)
{ {
u64 index = prop / 64; u64 index = prop / 64;
@ -115,4 +134,19 @@ INLINE b32 entity_has_prop(struct entity *entity, enum entity_prop prop)
return !!(entity->props[index] & ((u64)1 << bit)); return !!(entity->props[index] & ((u64)1 << bit));
} }
/* ========================== *
* Entity functions
* ========================== */
/* Entity store */
struct entity_store entity_store_alloc(void);
void entity_store_release(struct entity_store *store);
void entity_store_copy_replace(struct entity_store *dest, struct entity_store *src);
/* Entity */
struct entity *entity_alloc(struct entity_store *store);
void entity_release(struct entity_store *store, struct entity *entity);
struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle);
void entity_link(struct entity_store *store, struct entity *parent, struct entity *child);
#endif #endif

View File

@ -81,16 +81,12 @@ INTERNAL void game_update(void)
struct temp_arena scratch = scratch_begin_no_conflict(); struct temp_arena scratch = scratch_begin_no_conflict();
++L.world.tick_id;
L.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale);
L.world.time += L.world.dt;
/* TODO: remove this (testing) */ /* TODO: remove this (testing) */
/* Initialize entities */ /* Initialize entities */
static b32 run = 0; static b32 run = 0;
if (!run) { if (!run) {
run = 1; run = 1;
(UNUSED)world_link_entities; (UNUSED)entity_link;
/* Player ent */ /* Player ent */
struct entity *player_ent; struct entity *player_ent;
@ -99,7 +95,7 @@ INTERNAL void game_update(void)
struct v2 size = V2(1, 1); struct v2 size = V2(1, 1);
f32 r = 0; f32 r = 0;
struct entity *e = world_alloc_entity(&L.world); struct entity *e = entity_alloc(&L.world.entity_store);
e->valid = true; e->valid = true;
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size); e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
@ -152,7 +148,7 @@ INTERNAL void game_update(void)
struct v2 size = V2(1, 1); struct v2 size = V2(1, 1);
f32 r = 0; f32 r = 0;
struct entity *e = world_alloc_entity(&L.world); struct entity *e = entity_alloc(&L.world.entity_store);
e->valid = true; e->valid = true;
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size); e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
@ -192,11 +188,11 @@ INTERNAL void game_update(void)
//entity_enable_prop(e, ENTITY_PROP_TEST); //entity_enable_prop(e, ENTITY_PROP_TEST);
//entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE); //entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
world_link_entities(&L.world, parent, e); entity_link(&L.world.entity_store, parent, e);
if (sys_rand_u32() % 2 == 0) { if (sys_rand_u32() % 2 == 0) {
u64 parent_idx = sys_rand_u32() % L.world.entities_count; u64 parent_idx = sys_rand_u32() % world_get_entities(&L.world).count;
struct entity *rand_ent = world_entity_from_handle(&L.world, (struct entity_handle) { .idx = parent_idx, .gen = 1 }); struct entity *rand_ent = entity_from_handle(&L.world.entity_store, (struct entity_handle) { .idx = parent_idx, .gen = 1 });
if (rand_ent->valid) { if (rand_ent->valid) {
parent = rand_ent; parent = rand_ent;
} else { } else {
@ -207,7 +203,7 @@ INTERNAL void game_update(void)
/* Camera ent */ /* Camera ent */
{ {
struct entity *e = world_alloc_entity(&L.world); struct entity *e = entity_alloc(&L.world.entity_store);
e->valid = true; e->valid = true;
e->rel_xform = XFORM_IDENT; e->rel_xform = XFORM_IDENT;
@ -219,6 +215,11 @@ INTERNAL void game_update(void)
} }
} }
++L.world.tick_id;
L.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale);
L.world.time += L.world.dt;
struct entity_array entities_array = world_get_entities(&L.world);
/* ========================== * /* ========================== *
* Process game cmds * Process game cmds
* ========================== */ * ========================== */
@ -244,10 +245,10 @@ INTERNAL void game_update(void)
/* Clear level */ /* Clear level */
case GAME_CMD_KIND_CLEAR_ALL: { case GAME_CMD_KIND_CLEAR_ALL: {
for (u64 i = 0; i < L.world.entities_count; ++i) { for (u64 i = 0; i < entities_array.count; ++i) {
struct entity *ent = &L.world.entities[i]; struct entity *ent = &entities_array.entities[i];
if (ent->valid) { if (ent->valid) {
world_release_entity(&L.world, ent); entity_release(&L.world.entity_store, ent);
} }
} }
} break; } break;
@ -268,8 +269,8 @@ INTERNAL void game_update(void)
* Update entities pre-physics * Update entities pre-physics
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) { for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index]; struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue; if (!ent->valid) continue;
/* ========================== * /* ========================== *
@ -337,8 +338,8 @@ INTERNAL void game_update(void)
* Update entity physics * Update entity physics
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) { for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index]; struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue; if (!ent->valid) continue;
if (ent->parent.gen) continue; /* Only update parent entities */ if (ent->parent.gen) continue; /* Only update parent entities */
@ -417,14 +418,14 @@ INTERNAL void game_update(void)
child->world_xform = world_xform; child->world_xform = world_xform;
/* Append sub-children to stack */ /* Append sub-children to stack */
struct entity *subchild = world_entity_from_handle(&L.world, child->last); struct entity *subchild = entity_from_handle(&L.world.entity_store, child->last);
while (subchild->valid) { while (subchild->valid) {
*arena_push(stack.arena, struct xform_stack_node) = (struct xform_stack_node) { *arena_push(stack.arena, struct xform_stack_node) = (struct xform_stack_node) {
.entity = subchild, .entity = subchild,
.parent_xform = world_xform .parent_xform = world_xform
}; };
++stack_count; ++stack_count;
subchild = world_entity_from_handle(&L.world, subchild->prev); subchild = entity_from_handle(&L.world.entity_store, subchild->prev);
} }
} }
@ -436,8 +437,8 @@ INTERNAL void game_update(void)
* Update entities post-physics * Update entities post-physics
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) { for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index]; struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue; if (!ent->valid) continue;
/* ========================== * /* ========================== *
@ -445,7 +446,7 @@ INTERNAL void game_update(void)
* ========================== */ * ========================== */
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) { if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
struct entity *follow = world_entity_from_handle(&L.world, ent->camera_follow); struct entity *follow = entity_from_handle(&L.world.entity_store, ent->camera_follow);
ent->world_xform = follow->world_xform; ent->world_xform = follow->world_xform;
ent->world_xform = xform_with_rotation(ent->world_xform, 0); ent->world_xform = xform_with_rotation(ent->world_xform, 0);

View File

@ -5,7 +5,9 @@
#define MEMZERO_ARRAY(a) MEMZERO(a, sizeof(a)) #define MEMZERO_ARRAY(a) MEMZERO(a, sizeof(a))
#define MEMZERO(ptr, count) MEMSET(ptr, 0, count) #define MEMZERO(ptr, count) MEMSET(ptr, 0, count)
#define MEMCPY_STRUCT(ptr_dest, ptr_src) MEMCPY(ptr_dest, ptr_src, sizeof(*ptr_dest));
#define MEMCPY(dest, src, count) memcpy(dest, src, count) #define MEMCPY(dest, src, count) memcpy(dest, src, count)
#define MEMSET(ptr, val, count) memset(ptr, val, count) #define MEMSET(ptr, val, count) memset(ptr, val, count)

View File

@ -516,11 +516,15 @@ INTERNAL void user_update(void)
L.world.time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend); L.world.time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend);
/* Blend entities */ /* Blend entities */
u64 num_entities = min_u64(t0->entities_count, t1->entities_count); struct entity_array t0_entities = world_get_entities(t0);
struct entity_array t1_entities = world_get_entities(t1);
struct entity_array world_entities = world_get_entities(&L.world);
u64 num_entities = min_u64(t0_entities.count, t1_entities.count);
for (u64 i = 0; i < num_entities; ++i) { for (u64 i = 0; i < num_entities; ++i) {
struct entity *e0 = &t0->entities[i]; struct entity *e0 = &t0_entities.entities[i];
struct entity *e1 = &t1->entities[i]; struct entity *e1 = &t1_entities.entities[i];
struct entity *e = &L.world.entities[i]; struct entity *e = &world_entities.entities[i];
if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) { if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) {
e->rel_xform = xform_lerp(e0->rel_xform, e1->rel_xform, tick_blend); e->rel_xform = xform_lerp(e0->rel_xform, e1->rel_xform, tick_blend);
e->world_xform = xform_lerp(e0->world_xform, e1->world_xform, tick_blend); e->world_xform = xform_lerp(e0->world_xform, e1->world_xform, tick_blend);
@ -540,13 +544,15 @@ INTERNAL void user_update(void)
#endif #endif
} }
struct entity_array entities_array = world_get_entities(&L.world);
/* ========================== * /* ========================== *
* Update view from game camera * Update view from game camera
* ========================== */ * ========================== */
/* Find camera */ /* Find camera */
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) { for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index]; struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue; if (!ent->valid) continue;
if (entity_has_prop(ent, ENTITY_PROP_CAMERA) && ent->camera_active && !L.debug_camera) { if (entity_has_prop(ent, ENTITY_PROP_CAMERA) && ent->camera_active && !L.debug_camera) {
@ -606,10 +612,10 @@ INTERNAL void user_update(void)
* ========================== */ * ========================== */
/* Iterate entities */ /* Iterate entities */
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) { for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
__profscope(user_entity_iter); __profscope(user_entity_iter);
struct entity *ent = &L.world.entities[entity_index]; struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue; if (!ent->valid) continue;
b32 is_camera = entity_has_prop(ent, ENTITY_PROP_CAMERA); b32 is_camera = entity_has_prop(ent, ENTITY_PROP_CAMERA);
@ -721,7 +727,7 @@ INTERNAL void user_update(void)
debug_draw_movement(ent); debug_draw_movement(ent);
/* Draw hierarchy */ /* Draw hierarchy */
struct entity *parent = world_entity_from_handle(&L.world, ent->parent); struct entity *parent = entity_from_handle(&L.world.entity_store, ent->parent);
if (parent->valid) { if (parent->valid) {
u32 color = RGBA_F(0.6, 0.6, 1, 0.75); u32 color = RGBA_F(0.6, 0.6, 1, 0.75);
f32 thickness = 5; f32 thickness = 5;

View File

@ -1,97 +1,32 @@
#include "world.h" #include "world.h"
#include "entity.h" #include "entity.h"
#include "arena.h"
/* ========================== * #include "scratch.h"
* World allocation
* ========================== */
void world_alloc(struct world *world) void world_alloc(struct world *world)
{ {
MEMZERO_STRUCT(world); MEMZERO_STRUCT(world);
world->entities_arena = arena_alloc(GIGABYTE(64)); world->entity_store = entity_store_alloc();
world->entities = (struct entity *)world->entities_arena.base;
} }
void world_copy_replace(struct world *dest, struct world *src) void world_copy_replace(struct world *dest, struct world *src)
{ {
__prof; __prof;
struct temp_arena scratch = scratch_begin_no_conflict();
struct world *old = arena_push(scratch.arena, struct world);
*old = *dest;
/* Copy non-arena fields */ MEMCPY_STRUCT(dest, src);
MEMCPY(dest, src, FIELD_OFFSETOF(struct world, _copy_barrier)); dest->entity_store = old->entity_store;
entity_store_copy_replace(&dest->entity_store, &src->entity_store);
/* Copy arena contents */ scratch_end(scratch);
arena_copy_replace(&dest->entities_arena, &src->entities_arena);
} }
/* ========================== * struct entity_array world_get_entities(struct world *world)
* Entity allocation
* ========================== */
struct entity *world_alloc_entity(struct world *world)
{ {
struct entity *entity = NULL; return (struct entity_array) {
struct entity_handle handle; .entities = (struct entity *)world->entity_store.arena.base,
if (world->first_free_entity.gen) { .count = world->entity_store.count
/* Reuse from free list */
entity = world_entity_from_handle(world, world->first_free_entity);
world->first_free_entity = entity->next_free;
handle = entity->handle;
} else {
/* Make new */
u64 idx = world->entities_count++;
entity = arena_push(&world->entities_arena, struct entity);
handle = (struct entity_handle) { .gen = 1, .idx = idx };
}
*entity = *entity_nil();
entity->handle = handle;
return entity;
}
void world_release_entity(struct world *world, struct entity *entity)
{
struct entity_handle next_free = world->first_free_entity;
world->first_free_entity = entity->handle;
*entity = (struct entity) {
.handle.gen = entity->handle.gen + 1,
.next_free = next_free
}; };
} }
/* ========================== *
* Queries
* ========================== */
/* Returns a valid entity or nil entity. Always safe to read result, need to check to write. */
struct entity *world_entity_from_handle(struct world *world, struct entity_handle handle)
{
if (handle.idx < world->entities_count) {
struct entity *entities = world->entities;
struct entity *entity = &entities[handle.idx];
if (entity->handle.gen == handle.gen) {
return entity;
}
}
return entity_nil();
}
/* ========================== *
* Tree
* ========================== */
void world_link_entities(struct world *world, struct entity *parent, struct entity *child)
{
struct entity *first_child = world_entity_from_handle(world, parent->first);
struct entity *last_child = world_entity_from_handle(world, parent->last);
child->prev = last_child->handle;
child->parent = parent->handle;
if (last_child->valid) {
last_child->next = child->handle;
}
parent->last = child->handle;
if (!first_child->valid) {
parent->first = child->handle;
}
}

View File

@ -13,25 +13,11 @@ struct world {
struct v2 player_move_dir; /* Player movement direction */ struct v2 player_move_dir; /* Player movement direction */
struct v2 player_focus; /* Mouse cursor pos in world coordinates */ struct v2 player_focus; /* Mouse cursor pos in world coordinates */
u64 entities_count; /* Includes 'released' & inactive entities */ struct entity_store entity_store;
struct entity_handle first_free_entity;
/* ====================================================================== */
/* Everything after this field is not dumb-MEMCPY'd by world_copy_replace */
u8 _copy_barrier;
/* Entities array */
struct arena entities_arena;
struct entity *entities;
}; };
void world_alloc(struct world *world); void world_alloc(struct world *world);
void world_copy_replace(struct world *dest, struct world *src); void world_copy_replace(struct world *dest, struct world *src);
struct entity_array world_get_entities(struct world *world);
struct entity *world_alloc_entity(struct world *world);
void world_release_entity(struct world *world, struct entity *entity);
struct entity *world_entity_from_handle(struct world *world, struct entity_handle handle);
void world_link_entities(struct world *world, struct entity *parent, struct entity *child);
#endif #endif