separate entity handling from world to new entity_store
This commit is contained in:
parent
495d7c97ee
commit
efa639c971
91
src/entity.c
91
src/entity.c
@ -7,3 +7,94 @@ READONLY struct entity _g_entity_nil = {
|
||||
.sprite_xform = XFORM_IDENT,
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
44
src/entity.h
44
src/entity.h
@ -4,11 +4,6 @@
|
||||
#include "sheet.h"
|
||||
#include "mixer.h"
|
||||
|
||||
struct entity_handle {
|
||||
u64 idx;
|
||||
u64 gen;
|
||||
};
|
||||
|
||||
enum entity_prop {
|
||||
ENTITY_PROP_NONE,
|
||||
|
||||
@ -25,6 +20,17 @@ enum entity_prop {
|
||||
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 {
|
||||
b32 valid;
|
||||
struct entity_handle handle;
|
||||
@ -91,9 +97,22 @@ struct entity {
|
||||
f32 camera_zoom;
|
||||
};
|
||||
|
||||
struct entity_array {
|
||||
struct entity *entities;
|
||||
u64 count;
|
||||
};
|
||||
|
||||
/* ========================== *
|
||||
* Nil
|
||||
* ========================== */
|
||||
|
||||
extern READONLY struct entity _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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* 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
|
||||
|
||||
47
src/game.c
47
src/game.c
@ -81,16 +81,12 @@ INTERNAL void game_update(void)
|
||||
|
||||
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) */
|
||||
/* Initialize entities */
|
||||
static b32 run = 0;
|
||||
if (!run) {
|
||||
run = 1;
|
||||
(UNUSED)world_link_entities;
|
||||
(UNUSED)entity_link;
|
||||
|
||||
/* Player ent */
|
||||
struct entity *player_ent;
|
||||
@ -99,7 +95,7 @@ INTERNAL void game_update(void)
|
||||
struct v2 size = V2(1, 1);
|
||||
f32 r = 0;
|
||||
|
||||
struct entity *e = world_alloc_entity(&L.world);
|
||||
struct entity *e = entity_alloc(&L.world.entity_store);
|
||||
e->valid = true;
|
||||
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);
|
||||
f32 r = 0;
|
||||
|
||||
struct entity *e = world_alloc_entity(&L.world);
|
||||
struct entity *e = entity_alloc(&L.world.entity_store);
|
||||
e->valid = true;
|
||||
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_FOLLOW_MOUSE);
|
||||
|
||||
world_link_entities(&L.world, parent, e);
|
||||
entity_link(&L.world.entity_store, parent, e);
|
||||
|
||||
if (sys_rand_u32() % 2 == 0) {
|
||||
u64 parent_idx = sys_rand_u32() % L.world.entities_count;
|
||||
struct entity *rand_ent = world_entity_from_handle(&L.world, (struct entity_handle) { .idx = parent_idx, .gen = 1 });
|
||||
u64 parent_idx = sys_rand_u32() % world_get_entities(&L.world).count;
|
||||
struct entity *rand_ent = entity_from_handle(&L.world.entity_store, (struct entity_handle) { .idx = parent_idx, .gen = 1 });
|
||||
if (rand_ent->valid) {
|
||||
parent = rand_ent;
|
||||
} else {
|
||||
@ -207,7 +203,7 @@ INTERNAL void game_update(void)
|
||||
|
||||
/* Camera ent */
|
||||
{
|
||||
struct entity *e = world_alloc_entity(&L.world);
|
||||
struct entity *e = entity_alloc(&L.world.entity_store);
|
||||
e->valid = true;
|
||||
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
|
||||
* ========================== */
|
||||
@ -244,10 +245,10 @@ INTERNAL void game_update(void)
|
||||
|
||||
/* Clear level */
|
||||
case GAME_CMD_KIND_CLEAR_ALL: {
|
||||
for (u64 i = 0; i < L.world.entities_count; ++i) {
|
||||
struct entity *ent = &L.world.entities[i];
|
||||
for (u64 i = 0; i < entities_array.count; ++i) {
|
||||
struct entity *ent = &entities_array.entities[i];
|
||||
if (ent->valid) {
|
||||
world_release_entity(&L.world, ent);
|
||||
entity_release(&L.world.entity_store, ent);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@ -268,8 +269,8 @@ INTERNAL void game_update(void)
|
||||
* Update entities pre-physics
|
||||
* ========================== */
|
||||
|
||||
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
|
||||
struct entity *ent = &L.world.entities[entity_index];
|
||||
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
|
||||
struct entity *ent = &entities_array.entities[entity_index];
|
||||
if (!ent->valid) continue;
|
||||
|
||||
/* ========================== *
|
||||
@ -337,8 +338,8 @@ INTERNAL void game_update(void)
|
||||
* Update entity physics
|
||||
* ========================== */
|
||||
|
||||
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
|
||||
struct entity *ent = &L.world.entities[entity_index];
|
||||
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
|
||||
struct entity *ent = &entities_array.entities[entity_index];
|
||||
if (!ent->valid) continue;
|
||||
if (ent->parent.gen) continue; /* Only update parent entities */
|
||||
|
||||
@ -417,14 +418,14 @@ INTERNAL void game_update(void)
|
||||
child->world_xform = world_xform;
|
||||
|
||||
/* 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) {
|
||||
*arena_push(stack.arena, struct xform_stack_node) = (struct xform_stack_node) {
|
||||
.entity = subchild,
|
||||
.parent_xform = world_xform
|
||||
};
|
||||
++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
|
||||
* ========================== */
|
||||
|
||||
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
|
||||
struct entity *ent = &L.world.entities[entity_index];
|
||||
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
|
||||
struct entity *ent = &entities_array.entities[entity_index];
|
||||
if (!ent->valid) continue;
|
||||
|
||||
/* ========================== *
|
||||
@ -445,7 +446,7 @@ INTERNAL void game_update(void)
|
||||
* ========================== */
|
||||
|
||||
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 = xform_with_rotation(ent->world_xform, 0);
|
||||
|
||||
@ -5,7 +5,9 @@
|
||||
#define MEMZERO_ARRAY(a) MEMZERO(a, sizeof(a))
|
||||
#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 MEMSET(ptr, val, count) memset(ptr, val, count)
|
||||
|
||||
|
||||
|
||||
24
src/user.c
24
src/user.c
@ -516,11 +516,15 @@ INTERNAL void user_update(void)
|
||||
L.world.time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend);
|
||||
|
||||
/* 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) {
|
||||
struct entity *e0 = &t0->entities[i];
|
||||
struct entity *e1 = &t1->entities[i];
|
||||
struct entity *e = &L.world.entities[i];
|
||||
struct entity *e0 = &t0_entities.entities[i];
|
||||
struct entity *e1 = &t1_entities.entities[i];
|
||||
struct entity *e = &world_entities.entities[i];
|
||||
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->world_xform = xform_lerp(e0->world_xform, e1->world_xform, tick_blend);
|
||||
@ -540,13 +544,15 @@ INTERNAL void user_update(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
struct entity_array entities_array = world_get_entities(&L.world);
|
||||
|
||||
/* ========================== *
|
||||
* Update view from game camera
|
||||
* ========================== */
|
||||
|
||||
/* Find camera */
|
||||
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
|
||||
struct entity *ent = &L.world.entities[entity_index];
|
||||
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
|
||||
struct entity *ent = &entities_array.entities[entity_index];
|
||||
if (!ent->valid) continue;
|
||||
|
||||
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 */
|
||||
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);
|
||||
|
||||
struct entity *ent = &L.world.entities[entity_index];
|
||||
struct entity *ent = &entities_array.entities[entity_index];
|
||||
if (!ent->valid) continue;
|
||||
|
||||
b32 is_camera = entity_has_prop(ent, ENTITY_PROP_CAMERA);
|
||||
@ -721,7 +727,7 @@ INTERNAL void user_update(void)
|
||||
debug_draw_movement(ent);
|
||||
|
||||
/* 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) {
|
||||
u32 color = RGBA_F(0.6, 0.6, 1, 0.75);
|
||||
f32 thickness = 5;
|
||||
|
||||
93
src/world.c
93
src/world.c
@ -1,97 +1,32 @@
|
||||
#include "world.h"
|
||||
#include "entity.h"
|
||||
|
||||
/* ========================== *
|
||||
* World allocation
|
||||
* ========================== */
|
||||
#include "arena.h"
|
||||
#include "scratch.h"
|
||||
|
||||
void world_alloc(struct world *world)
|
||||
{
|
||||
MEMZERO_STRUCT(world);
|
||||
world->entities_arena = arena_alloc(GIGABYTE(64));
|
||||
world->entities = (struct entity *)world->entities_arena.base;
|
||||
world->entity_store = entity_store_alloc();
|
||||
}
|
||||
|
||||
void world_copy_replace(struct world *dest, struct world *src)
|
||||
{
|
||||
__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(dest, src, FIELD_OFFSETOF(struct world, _copy_barrier));
|
||||
MEMCPY_STRUCT(dest, src);
|
||||
dest->entity_store = old->entity_store;
|
||||
entity_store_copy_replace(&dest->entity_store, &src->entity_store);
|
||||
|
||||
/* Copy arena contents */
|
||||
arena_copy_replace(&dest->entities_arena, &src->entities_arena);
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Entity allocation
|
||||
* ========================== */
|
||||
|
||||
struct entity *world_alloc_entity(struct world *world)
|
||||
struct entity_array world_get_entities(struct world *world)
|
||||
{
|
||||
struct entity *entity = NULL;
|
||||
struct entity_handle handle;
|
||||
if (world->first_free_entity.gen) {
|
||||
/* 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
|
||||
return (struct entity_array) {
|
||||
.entities = (struct entity *)world->entity_store.arena.base,
|
||||
.count = world->entity_store.count
|
||||
};
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
18
src/world.h
18
src/world.h
@ -13,25 +13,11 @@ struct world {
|
||||
struct v2 player_move_dir; /* Player movement direction */
|
||||
struct v2 player_focus; /* Mouse cursor pos in world coordinates */
|
||||
|
||||
u64 entities_count; /* Includes 'released' & inactive entities */
|
||||
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;
|
||||
struct entity_store entity_store;
|
||||
};
|
||||
|
||||
void world_alloc(struct world *world);
|
||||
void world_copy_replace(struct world *dest, struct world *src);
|
||||
|
||||
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);
|
||||
struct entity_array world_get_entities(struct world *world);
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue
Block a user