begin entity xform tree refactor

This commit is contained in:
jacob 2024-08-06 13:44:24 -05:00
parent bda472d15e
commit c7ee34037a
7 changed files with 159 additions and 66 deletions

BIN
res/graphics/tim.ase (Stored with Git LFS)

Binary file not shown.

View File

@ -1,7 +1,13 @@
#include "entity.h" #include "entity.h"
#include "math.h" #include "math.h"
/* Accessed via entity_store_nil() */
READONLY struct entity_store _g_entity_store_nil = {
0
};
/* Accessed via entity_nil() */ /* Accessed via entity_nil() */
/* TODO: Allocate nil entity in nil store */
READONLY struct entity _g_entity_nil = { READONLY struct entity _g_entity_nil = {
.xform = XFORM_IDENT_NOCAST, .xform = XFORM_IDENT_NOCAST,
.xform_world = XFORM_IDENT_NOCAST, .xform_world = XFORM_IDENT_NOCAST,
@ -13,10 +19,19 @@ READONLY struct entity _g_entity_nil = {
* Store allocation * Store allocation
* ========================== */ * ========================== */
struct entity_store entity_store_alloc(void) struct entity_store *entity_store_alloc(void)
{ {
struct entity_store store = { 0 }; struct arena arena = arena_alloc(GIGABYTE(64));
store.arena = arena_alloc(GIGABYTE(64)); struct entity_store *store = arena_push_zero(&arena, struct entity_store);
store->arena = arena;
store->entities = arena_dry_push(&arena, struct entity);
/* Special entity for keeping track of store offset */
struct entity *offset_entity = entity_alloc(store);
offset_entity->valid = false;
offset_entity->store_offset = (u64)store->entities - (u64)store;
ASSERT(offset_entity->handle.idx == 0); /* Must be first entity in array */
return store; return store;
} }
@ -27,10 +42,14 @@ void entity_store_release(struct entity_store *store)
void entity_store_copy_replace(struct entity_store *dest, struct entity_store *src) void entity_store_copy_replace(struct entity_store *dest, struct entity_store *src)
{ {
struct arena orig_arena = dest->arena; struct arena dest_arena = dest->arena;
struct entity *dest_entities = dest->entities;
u64 dest_store_offset = dest_entities[0].store_offset;
MEMCPY_STRUCT(dest, src); MEMCPY_STRUCT(dest, src);
dest->arena = orig_arena; arena_copy_replace(&dest_arena, &src->arena);
arena_copy_replace(&dest->arena, &src->arena); dest->arena = dest_arena;
dest->entities = dest_entities;
dest->entities[0].store_offset = dest_store_offset;
} }
/* ========================== * /* ========================== *
@ -40,48 +59,41 @@ void entity_store_copy_replace(struct entity_store *dest, struct entity_store *s
struct entity *entity_alloc(struct entity_store *store) struct entity *entity_alloc(struct entity_store *store)
{ {
struct entity *entity = NULL; struct entity *entity = NULL;
struct entity_handle handle = { 0 };
if (store->first_free.gen) { if (store->first_free.gen) {
/* Reuse from free list */ /* Reuse from free list */
entity = entity_from_handle(store, store->first_free); entity = entity_from_handle(store, store->first_free);
handle = entity->handle;
store->first_free = entity->next_free; store->first_free = entity->next_free;
entity->next_free = (struct entity_handle) { 0 };
} else { } else {
/* Make new */ /* Make new */
u64 idx = store->count++;
entity = arena_push(&store->arena, struct entity); entity = arena_push(&store->arena, struct entity);
*entity = *entity_nil(); handle = (struct entity_handle) { .gen = 1, .idx = store->count++ };
entity->handle = (struct entity_handle) { .gen = 1, .idx = idx };
} }
*entity = *entity_nil();
entity->handle = handle;
entity->valid = true;
return entity; return entity;
} }
void entity_release(struct entity_store *store, struct entity *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 }; /* FIXME: Unlink entity */
*entity = *entity_nil(); ++entity->handle.gen;
entity->handle = handle; entity->valid = false;
entity->next_free = store->first_free; entity->next_free = store->first_free;
store->first_free = handle; store->first_free = entity->handle;
} }
/* ========================== * /* ========================== *
* Query * Query
* ========================== */ * ========================== */
struct entity_array entity_store_as_array(struct entity_store *store) /* Returns a valid entity or nil entity. Always safe to read result, need to check `valid` to write. */
{
return (struct entity_array) {
.entities = (struct entity *)store->arena.base,
.count = store->count
};
}
/* 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) struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle)
{ {
if (handle.idx < store->count) { if (handle.idx < store->count) {
struct entity *entities = (struct entity *)store->arena.base; struct entity *entity = &store->entities[handle.idx];
struct entity *entity = &entities[handle.idx];
if (entity->handle.gen == handle.gen) { if (entity->handle.gen == handle.gen) {
return entity; return entity;
} }
@ -91,9 +103,10 @@ struct entity *entity_from_handle(struct entity_store *store, struct entity_hand
struct entity *entity_find_first_match_one(struct entity_store *store, enum entity_prop prop) struct entity *entity_find_first_match_one(struct entity_store *store, enum entity_prop prop)
{ {
struct entity_array entities_array = entity_store_as_array(store); u64 count = store->count;
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) { struct entity *entities = store->entities;
struct entity *ent = &entities_array.entities[entity_index]; for (u64 entity_index = 0; entity_index < count; ++entity_index) {
struct entity *ent = &entities[entity_index];
if (ent->valid && entity_has_prop(ent, prop)) { if (ent->valid && entity_has_prop(ent, prop)) {
return ent; return ent;
} }
@ -103,9 +116,10 @@ struct entity *entity_find_first_match_one(struct entity_store *store, enum enti
struct entity *entity_find_first_match_all(struct entity_store *store, struct entity_prop_array props) struct entity *entity_find_first_match_all(struct entity_store *store, struct entity_prop_array props)
{ {
struct entity_array entities_array = entity_store_as_array(store); u64 count = store->count;
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) { struct entity *entities = store->entities;
struct entity *ent = &entities_array.entities[entity_index]; for (u64 entity_index = 0; entity_index < count; ++entity_index) {
struct entity *ent = &entities[entity_index];
if (ent->valid) { if (ent->valid) {
b32 all = true; b32 all = true;
for (u64 i = 0; i < props.count; ++i) { for (u64 i = 0; i < props.count; ++i) {
@ -122,6 +136,62 @@ struct entity *entity_find_first_match_all(struct entity_store *store, struct en
return entity_nil(); return entity_nil();
} }
/* ========================== *
* Xform
* ========================== */
/* TODO: Move this */
INTERNAL struct entity_store *entity_get_store(struct entity *ent)
{
struct entity_store *store = entity_store_nil();
if (ent->valid) {
struct entity *first_entity = ent - ent->handle.idx;
u64 store_offset = first_entity->store_offset;
store = (struct entity_store *)((u64)first_entity - store_offset);
ASSERT(store->entities == first_entity);
}
return store;
}
struct xform entity_get_local_xform(struct entity *ent)
{
return ent->xform;
}
void entity_set_local_xform(struct entity *ent, struct xform xf)
{
ent->xform = xf;
}
struct xform entity_get_global_xform(struct entity *ent)
{
/* TODO: Remove this */
return ent->xform_world;
}
void entity_set_global_xform(struct entity *ent, struct xform xf)
{
/* TODO: Remove this */
struct entity_store *store = entity_get_store(ent);
struct entity *parent = entity_from_handle(store, ent->parent);
struct xform parent_global = parent->xform_world;
struct xform child_global = xf;
struct xform child_local = xform_mul(xform_invert(parent_global), child_global);
ent->xform = child_local;
ent->xform_world = child_global;
}
/* ========================== * /* ========================== *
* Tree * Tree
* ========================== */ * ========================== */
@ -143,3 +213,5 @@ void entity_link(struct entity_store *store, struct entity *parent, struct entit
parent->first = child->handle; parent->first = child->handle;
} }
} }
/* TODO: entity_unlink() */

View File

@ -29,16 +29,20 @@ struct entity_store {
struct arena arena; struct arena arena;
u64 count; u64 count;
struct entity_handle first_free; struct entity_handle first_free;
struct entity *entities;
}; };
struct entity { struct entity {
/* Metadata */
b32 valid; b32 valid;
struct entity_handle handle; struct entity_handle handle;
u64 continuity_gen; u64 continuity_gen;
u64 props[(ENTITY_PROP_COUNT + 63) / 64]; u64 props[(ENTITY_PROP_COUNT + 63) / 64];
struct entity_handle next_free; struct entity_handle next_free;
/* Special value stored in first entity in store array */
u64 store_offset;
/* Tree */ /* Tree */
struct entity_handle parent; struct entity_handle parent;
struct entity_handle next; struct entity_handle next;
@ -124,6 +128,12 @@ INLINE struct entity *entity_nil(void)
return &_g_entity_nil; return &_g_entity_nil;
} }
INLINE struct entity_store *entity_store_nil(void)
{
extern READONLY struct entity_store _g_entity_store_nil;
return &_g_entity_store_nil;
}
/* ========================== * /* ========================== *
* Property helpers * Property helpers
* ========================== */ * ========================== */
@ -153,8 +163,10 @@ INLINE b32 entity_has_prop(struct entity *entity, enum entity_prop prop)
* Entity functions * Entity functions
* ========================== */ * ========================== */
INLINE struct entity_array entity_store_as_array(struct entity_store *store) { return (struct entity_array) { .count = store->count, .entities = store->entities }; }
/* Entity store */ /* Entity store */
struct entity_store entity_store_alloc(void); struct entity_store *entity_store_alloc(void);
void entity_store_release(struct entity_store *store); void entity_store_release(struct entity_store *store);
void entity_store_copy_replace(struct entity_store *dest, struct entity_store *src); void entity_store_copy_replace(struct entity_store *dest, struct entity_store *src);
@ -162,11 +174,18 @@ void entity_store_copy_replace(struct entity_store *dest, struct entity_store *s
struct entity *entity_alloc(struct entity_store *store); struct entity *entity_alloc(struct entity_store *store);
void entity_release(struct entity_store *store, struct entity *entity); void entity_release(struct entity_store *store, struct entity *entity);
struct entity_array entity_store_as_array(struct entity_store *store); /* Xform */
struct xform entity_get_local_xform(struct entity *ent);
void entity_set_local_xform(struct entity *ent, struct xform xf);
struct xform entity_get_global_xform(struct entity *ent);
void entity_set_global_xform(struct entity *ent, struct xform xf);
/* Query */
struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle); struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle);
struct entity *entity_find_first_match_one(struct entity_store *store, enum entity_prop prop); struct entity *entity_find_first_match_one(struct entity_store *store, enum entity_prop prop);
struct entity *entity_find_first_match_all(struct entity_store *store, struct entity_prop_array props); struct entity *entity_find_first_match_all(struct entity_store *store, struct entity_prop_array props);
/* Tree */
void entity_link(struct entity_store *store, struct entity *parent, struct entity *child); void entity_link(struct entity_store *store, struct entity *parent, struct entity *child);
#endif #endif

View File

@ -137,11 +137,11 @@ INTERNAL void recalculate_world_xforms_recurse(struct entity *parent)
child->sprite_xform_world = sprite_xform_world; child->sprite_xform_world = sprite_xform_world;
/* Append sub-children to stack */ /* Append sub-children to stack */
struct entity *subchild = entity_from_handle(&G.world.entity_store, child->last); struct entity *subchild = entity_from_handle(G.world.entity_store, child->last);
while (subchild->valid) { while (subchild->valid) {
*arena_push(scratch.arena, struct stack_node) = (struct stack_node) { .entity = subchild, .parent_xform_world = xform_world }; *arena_push(scratch.arena, struct stack_node) = (struct stack_node) { .entity = subchild, .parent_xform_world = xform_world };
++stack_count; ++stack_count;
subchild = entity_from_handle(&G.world.entity_store, subchild->prev); subchild = entity_from_handle(G.world.entity_store, subchild->prev);
} }
} }
@ -173,14 +173,13 @@ 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 = entity_alloc(&G.world.entity_store); struct entity *e = entity_alloc(G.world.entity_store);
e->valid = true;
e->xform = XFORM_TRS(.t = pos, .r = r, .s = size); e->xform = XFORM_TRS(.t = pos, .r = r, .s = size);
e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase")); e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase"));
//e->sprite_span_name = STR("idle.unarmed"); e->sprite_span_name = STR("idle.unarmed");
//e->sprite_span_name = STR("idle.one_handed"); //e->sprite_span_name = STR("idle.one_handed");
e->sprite_span_name = STR("idle.two_handed"); //e->sprite_span_name = STR("idle.two_handed");
entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
e->player_max_speed = 4.f; e->player_max_speed = 4.f;
@ -196,8 +195,7 @@ INTERNAL void game_update(void)
/* Camera ent */ /* Camera ent */
{ {
struct entity *e = entity_alloc(&G.world.entity_store); struct entity *e = entity_alloc(G.world.entity_store);
e->valid = true;
e->xform = XFORM_IDENT; e->xform = XFORM_IDENT;
entity_enable_prop(e, ENTITY_PROP_CAMERA); entity_enable_prop(e, ENTITY_PROP_CAMERA);
@ -214,7 +212,7 @@ INTERNAL void game_update(void)
G.world.tick_ts = sys_timestamp(); G.world.tick_ts = sys_timestamp();
G.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * G.world.timescale); G.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * G.world.timescale);
G.world.time += G.world.dt; G.world.time += G.world.dt;
struct entity_array entities_array = entity_store_as_array(&G.world.entity_store); struct entity_array entities_array = entity_store_as_array(G.world.entity_store);
/* ========================== * /* ========================== *
* Process game cmds * Process game cmds
@ -240,7 +238,7 @@ INTERNAL void game_update(void)
for (u64 i = 0; i < entities_array.count; ++i) { for (u64 i = 0; i < entities_array.count; ++i) {
struct entity *ent = &entities_array.entities[i]; struct entity *ent = &entities_array.entities[i];
if (ent->valid) { if (ent->valid) {
entity_release(&G.world.entity_store, ent); entity_release(G.world.entity_store, ent);
} }
} }
} break; } break;
@ -338,7 +336,7 @@ INTERNAL void game_update(void)
/* ENTITY_PROP_TEST */ /* ENTITY_PROP_TEST */
if (entity_has_prop(ent, ENTITY_PROP_TEST) && !ent->test_initialized) { if (entity_has_prop(ent, ENTITY_PROP_TEST) && !ent->test_initialized) {
ent->test_initialized = true; ent->test_initialized = true;
ent->test_start_rel_xform = ent->xform; ent->test_start_rel_xform = entity_get_local_xform(ent);
ent->test_start_sprite_xform = ent->sprite_xform; ent->test_start_sprite_xform = ent->sprite_xform;
} }
@ -350,14 +348,14 @@ INTERNAL void game_update(void)
/* Update focus */ /* Update focus */
ent->focus = G.world.player_aim; ent->focus = G.world.player_aim;
/* Solve for missing angle using law of sines */ struct xform xf = entity_get_global_xform(ent);
/* Solve for final angle using law of sines */
f32 final_xf_angle; f32 final_xf_angle;
{ {
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("hold"), ent->animation_frame); struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("hold"), ent->animation_frame);
struct xform xf = ent->xform_world;
f32 forward_hold_angle_offset; f32 forward_hold_angle_offset;
{ {
struct xform xf_unrotated = xform_with_rotation(xf, 0); struct xform xf_unrotated = xform_with_rotation(xf, 0);
@ -384,7 +382,7 @@ INTERNAL void game_update(void)
} }
if (!F32_IS_NAN(final_xf_angle)) { if (!F32_IS_NAN(final_xf_angle)) {
ent->xform = xform_with_rotation(ent->xform_world, final_xf_angle); entity_set_global_xform(ent, xform_with_rotation(ent->xform_world, final_xf_angle));
} }
} }
@ -407,9 +405,11 @@ INTERNAL void game_update(void)
s = v2_add(s, xform_get_scale(ent->test_start_rel_xform)); s = v2_add(s, xform_get_scale(ent->test_start_rel_xform));
ent->xform.og = og; struct xform xf = entity_get_local_xform(ent);
ent->xform = xform_with_rotation(ent->xform, r); xf.og = og;
ent->xform = xform_with_scale(ent->xform, s); xf= xform_with_rotation(xf, r);
xf= xform_with_scale(ent->xform, s);
entity_set_local_xform(ent, xf);
} }
} }
@ -454,7 +454,9 @@ INTERNAL void game_update(void)
ent->velocity = v2_add(ent->velocity, a); ent->velocity = v2_add(ent->velocity, a);
/* Apply velocity to position */ /* Apply velocity to position */
ent->xform.og = v2_add(ent->xform.og, v2_mul(ent->velocity, dt)); struct xform xf = entity_get_global_xform(ent);
xf.og = v2_add(xf.og, v2_mul(ent->velocity, dt));
entity_set_global_xform(ent, xf);
} }
} }
@ -480,12 +482,12 @@ INTERNAL void game_update(void)
/* Camera follow */ /* Camera follow */
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) { if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
struct entity *follow = entity_from_handle(&G.world.entity_store, ent->camera_follow); struct entity *follow = entity_from_handle(G.world.entity_store, ent->camera_follow);
if (entity_has_prop(follow, ENTITY_PROP_PLAYER_CONTROLLED)) { if (entity_has_prop(follow, ENTITY_PROP_PLAYER_CONTROLLED)) {
f32 aspect_ratio = 1.0; f32 aspect_ratio = 1.0;
{ {
struct xform quad_xf = xform_mul(ent->xform_world, ent->camera_quad_xform); struct xform quad_xf = xform_mul(entity_get_global_xform(ent), ent->camera_quad_xform);
struct v2 camera_size = xform_get_scale(quad_xf); struct v2 camera_size = xform_get_scale(quad_xf);
if (!v2_eq(camera_size, V2(0, 0))) { if (!v2_eq(camera_size, V2(0, 0))) {
aspect_ratio = camera_size.x / camera_size.y; aspect_ratio = camera_size.x / camera_size.y;
@ -494,7 +496,7 @@ INTERNAL void game_update(void)
f32 ratio_y = 0.33f; f32 ratio_y = 0.33f;
f32 ratio_x = ratio_y / aspect_ratio; f32 ratio_x = ratio_y / aspect_ratio;
struct v2 camera_focus_dir = v2_mul_v2(follow->focus, V2(ratio_x, ratio_y)); struct v2 camera_focus_dir = v2_mul_v2(follow->focus, V2(ratio_x, ratio_y));
struct v2 camera_focus_pos = v2_add(follow->xform_world.og, camera_focus_dir); struct v2 camera_focus_pos = v2_add(entity_get_global_xform(follow).og, camera_focus_dir);
ent->camera_rel_xform_target = ent->xform; ent->camera_rel_xform_target = ent->xform;
ent->camera_rel_xform_target.og = camera_focus_pos; ent->camera_rel_xform_target.og = camera_focus_pos;
} }

View File

@ -438,9 +438,9 @@ INTERNAL void user_update(void)
G.world.player_aim = v2_lerp(t0->player_aim, t1->player_aim, tick_blend); G.world.player_aim = v2_lerp(t0->player_aim, t1->player_aim, tick_blend);
/* Blend entities */ /* Blend entities */
struct entity_array t0_entities = entity_store_as_array(&t0->entity_store); struct entity_array t0_entities = entity_store_as_array(t0->entity_store);
struct entity_array t1_entities = entity_store_as_array(&t1->entity_store); struct entity_array t1_entities = entity_store_as_array(t1->entity_store);
struct entity_array world_entities = entity_store_as_array(&G.world.entity_store); struct entity_array world_entities = entity_store_as_array(G.world.entity_store);
u64 num_entities = min_u64(t0_entities.count, t1_entities.count); 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) {
@ -471,7 +471,7 @@ INTERNAL void user_update(void)
tick_is_first_frame = G.world.tick_id == 0; tick_is_first_frame = G.world.tick_id == 0;
#endif #endif
} }
struct entity_array entities_array = entity_store_as_array(&G.world.entity_store); struct entity_array entities_array = entity_store_as_array(G.world.entity_store);
/* ========================== * /* ========================== *
* Find important entities * Find important entities
@ -870,7 +870,7 @@ INTERNAL void user_update(void)
} }
/* Draw hierarchy */ /* Draw hierarchy */
struct entity *parent = entity_from_handle(&G.world.entity_store, ent->parent); struct entity *parent = entity_from_handle(G.world.entity_store, ent->parent);
if (parent->valid) { if (parent->valid) {
u32 color = RGBA_32_F(0.6, 0.6, 1, 0.75); u32 color = RGBA_32_F(0.6, 0.6, 1, 0.75);
f32 thickness = 5; f32 thickness = 5;

View File

@ -18,7 +18,7 @@ void world_copy_replace(struct world *dest, struct world *src)
MEMCPY_STRUCT(dest, src); MEMCPY_STRUCT(dest, src);
dest->entity_store = old->entity_store; dest->entity_store = old->entity_store;
entity_store_copy_replace(&dest->entity_store, &src->entity_store); entity_store_copy_replace(dest->entity_store, src->entity_store);
scratch_end(scratch); scratch_end(scratch);
} }

View File

@ -15,7 +15,7 @@ struct world {
struct v2 player_move_dir; struct v2 player_move_dir;
struct v2 player_aim; struct v2 player_aim;
struct entity_store entity_store; struct entity_store *entity_store;
}; };
void world_alloc(struct world *world); void world_alloc(struct world *world);