diff --git a/src/entity.c b/src/entity.c index 9a169c97..b536b97b 100644 --- a/src/entity.c +++ b/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; + } +} diff --git a/src/entity.h b/src/entity.h index 426914b6..2681c501 100644 --- a/src/entity.h +++ b/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 diff --git a/src/game.c b/src/game.c index fe9e3190..cb35f8e6 100644 --- a/src/game.c +++ b/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); diff --git a/src/memory.h b/src/memory.h index 47825d9f..16b09c1b 100644 --- a/src/memory.h +++ b/src/memory.h @@ -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) diff --git a/src/user.c b/src/user.c index 074bf484..6daae5be 100644 --- a/src/user.c +++ b/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; diff --git a/src/world.c b/src/world.c index 77e6648f..0d520bc2 100644 --- a/src/world.c +++ b/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; - } -} diff --git a/src/world.h b/src/world.h index 2494a6df..256ded9b 100644 --- a/src/world.h +++ b/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