cleanup collision debug leak & merge structure with contact lookup into entity lookup

This commit is contained in:
jacob 2025-01-06 17:09:39 -06:00
parent 6268e012f6
commit 02290601c3
2 changed files with 127 additions and 104 deletions

View File

@ -35,6 +35,7 @@
#define GAME_PHYSICS_SUBSTEPS 4
#define GAME_PHYSICS_ENABLE_WARM_STARTING 1
#define GAME_PHYSICS_ENABLE_RELAXATION 1
#define GAME_PHYSICS_ENABLE_TOI 1
#define USER_DRAW_MENKOWSKI 0
#define GAME_PHYSICS_ENABLE_COLLISION 1

View File

@ -12,32 +12,29 @@
#include "log.h"
#include "collider.h"
struct contact_lookup_entry {
struct entity_lookup_key {
u64 hash;
struct entity_handle contact_ent_handle;
struct contact_lookup_entry *next;
struct contact_lookup_entry *prev;
};
struct contact_lookup_bucket {
struct contact_lookup_entry *first;
struct contact_lookup_entry *last;
struct entity_lookup_entry {
struct entity_lookup_key key;
struct entity_handle entity;
struct entity_lookup_entry *next;
struct entity_lookup_entry *prev;
};
struct contact_lookup {
struct entity_lookup_bucket {
struct entity_lookup_entry *first;
struct entity_lookup_entry *last;
};
struct entity_lookup {
struct arena arena;
struct contact_lookup_bucket buckets[4096];
struct contact_lookup_entry *first_free_entry;
struct entity_lookup_bucket *buckets;
u64 num_buckets;
struct entity_lookup_entry *first_free_entry;
};
#if COLLIDER_DEBUG
/* TODO: Remove this (debugging) */
struct collision_debug_lookup {
struct arena arena;
struct fixed_dict dict;
};
#endif
GLOBAL struct {
struct atomic_i32 game_thread_shutdown;
struct sys_thread game_thread;
@ -64,9 +61,9 @@ GLOBAL struct {
f32 mouse_joint_max_force;
/* Bookkeeping structures */
struct contact_lookup contact_lookup;
struct entity_lookup contact_lookup;
#if COLLIDER_DEBUG
struct collision_debug_lookup collision_debug_lookup;
struct entity_lookup collision_debug_lookup;
#endif
/* Ticks */
@ -176,36 +173,31 @@ INTERNAL void activate_now(struct entity *ent)
}
/* ========================== *
* Contact lookup
* Entity lookup
* ========================== */
INTERNAL void contact_lookup_alloc(struct contact_lookup *l)
INTERNAL struct entity_lookup entity_lookup_alloc(u64 num_buckets)
{
MEMZERO_STRUCT(l);
l->arena = arena_alloc(GIGABYTE(64));
ASSERT(num_buckets > 0);
struct entity_lookup l = ZI;
l.arena = arena_alloc(GIGABYTE(64));
l.buckets = arena_push_array_zero(&l.arena, struct entity_lookup_bucket, num_buckets);
l.num_buckets = num_buckets;
return l;
}
INTERNAL void contact_lookup_release(struct contact_lookup *l)
INTERNAL void entity_lookup_release(struct entity_lookup *l)
{
arena_release(&l->arena);
}
INTERNAL u64 contact_lookup_hash_from_entities(struct entity_handle h0, struct entity_handle h1)
INTERNAL struct entity_lookup_entry *entity_lookup_get(struct entity_lookup *l, struct entity_lookup_key key)
{
struct buffer b0 = BUFFER_FROM_STRUCT(&h0);
struct buffer b1 = BUFFER_FROM_STRUCT(&h1);
u64 hash = hash_fnv64(HASH_FNV64_BASIS, b0);
hash = hash_fnv64(hash, b1);
return hash;
}
INTERNAL struct contact_lookup_entry *contact_lookup_get(struct contact_lookup *l, u64 hash)
{
u64 index = hash % ARRAY_COUNT(l->buckets);
struct contact_lookup_bucket *bucket = &l->buckets[index];
struct contact_lookup_entry *res = NULL;
for (struct contact_lookup_entry *e = bucket->first; e; e = e->next) {
if (e->hash == hash) {
u64 index = key.hash % l->num_buckets;
struct entity_lookup_bucket *bucket = &l->buckets[index];
struct entity_lookup_entry *res = NULL;
for (struct entity_lookup_entry *e = bucket->first; e; e = e->next) {
if (e->key.hash == key.hash) {
res = e;
break;
}
@ -213,25 +205,25 @@ INTERNAL struct contact_lookup_entry *contact_lookup_get(struct contact_lookup *
return res;
}
INTERNAL void contact_lookup_set(struct contact_lookup *l, u64 hash, struct entity_handle handle)
INTERNAL void entity_lookup_set(struct entity_lookup *l, struct entity_lookup_key key, struct entity_handle handle)
{
u64 index = hash % ARRAY_COUNT(l->buckets);
struct contact_lookup_bucket *bucket = &l->buckets[index];
u64 index = key.hash % l->num_buckets;
struct entity_lookup_bucket *bucket = &l->buckets[index];
struct contact_lookup_entry *prev = NULL;
struct contact_lookup_entry **slot = &bucket->first;
struct entity_lookup_entry *prev = NULL;
struct entity_lookup_entry **slot = &bucket->first;
while (*slot) {
if ((*slot)->hash == hash) {
if ((*slot)->key.hash == key.hash) {
break;
}
prev = *slot;
slot = &(*slot)->next;
}
struct contact_lookup_entry *entry = *slot;
struct entity_lookup_entry *entry = *slot;
if (entry) {
/* Set existing entry */
entry->contact_ent_handle = handle;
entry->entity = handle;
} else {
/* Allocate entry */
if (l->first_free_entry) {
@ -239,12 +231,12 @@ INTERNAL void contact_lookup_set(struct contact_lookup *l, u64 hash, struct enti
l->first_free_entry->prev = NULL;
l->first_free_entry = entry->next;
} else {
entry = arena_push(&l->arena, struct contact_lookup_entry);
entry = arena_push(&l->arena, struct entity_lookup_entry);
}
MEMZERO_STRUCT(entry);
entry->hash = hash;
entry->contact_ent_handle = handle;
entry->key = key;
entry->entity = handle;
if (prev) {
entry->prev = prev;
prev->next = entry;
@ -255,11 +247,11 @@ INTERNAL void contact_lookup_set(struct contact_lookup *l, u64 hash, struct enti
}
}
INTERNAL void contact_lookup_remove(struct contact_lookup *l, struct contact_lookup_entry *entry)
INTERNAL void entity_lookup_remove(struct entity_lookup *l, struct entity_lookup_entry *entry)
{
struct contact_lookup_bucket *bucket = &l->buckets[entry->hash % ARRAY_COUNT(l->buckets)];
struct contact_lookup_entry *prev = entry->prev;
struct contact_lookup_entry *next = entry->next;
struct entity_lookup_bucket *bucket = &l->buckets[entry->key.hash % l->num_buckets];
struct entity_lookup_entry *prev = entry->prev;
struct entity_lookup_entry *next = entry->next;
if (prev) {
prev->next = next;
@ -281,6 +273,16 @@ INTERNAL void contact_lookup_remove(struct contact_lookup *l, struct contact_loo
l->first_free_entry = entry;
}
INTERNAL struct entity_lookup_key entity_lookup_key_from_two_handles(struct entity_handle h0, struct entity_handle h1)
{
struct entity_lookup_key key = ZI;
struct buffer b0 = BUFFER_FROM_STRUCT(&h0);
struct buffer b1 = BUFFER_FROM_STRUCT(&h1);
key.hash = hash_fnv64(HASH_FNV64_BASIS, b0);
key.hash = hash_fnv64(key.hash, b1);
return key;
}
/* ========================== *
* Reset
* ========================== */
@ -291,17 +293,14 @@ INTERNAL void reset_world(void)
/* Release world */
world_release(&G.tick);
/* Release bookkeeping */
#if COLLIDER_DEBUG
arena_release(&G.collision_debug_lookup.arena);
#endif
contact_lookup_release(&G.contact_lookup);
entity_lookup_release(&G.collision_debug_lookup);
entity_lookup_release(&G.contact_lookup);
}
/* Create bookkeeping */
contact_lookup_alloc(&G.contact_lookup);
G.contact_lookup = entity_lookup_alloc(4096);
#if COLLIDER_DEBUG
G.collision_debug_lookup.arena = arena_alloc(GIGABYTE(64));
G.collision_debug_lookup.dict = fixed_dict_init(&G.collision_debug_lookup.arena, 4096);
G.collision_debug_lookup = entity_lookup_alloc(4096);
#endif
/* Re-create world */
@ -507,12 +506,12 @@ INTERNAL void create_contacts(void)
e1_collider = check0_collider;
}
u64 lookup_hash = contact_lookup_hash_from_entities(e0->handle, e1->handle);
struct contact_lookup_entry *entry = contact_lookup_get(&G.contact_lookup, lookup_hash);
struct entity_lookup_key key = entity_lookup_key_from_two_handles(e0->handle, e1->handle);
struct entity_lookup_entry *entry = entity_lookup_get(&G.contact_lookup, key);
struct entity *constraint_ent = entity_nil();
if (entry) {
constraint_ent = entity_from_handle(store, entry->contact_ent_handle);
constraint_ent = entity_from_handle(store, entry->entity);
if (entity_is_valid_and_active(constraint_ent)) {
if (constraint_ent->contact_constraint_data.last_iteration >= G.tick.tick_id) {
/* Already processed constraint this iteration */
@ -522,7 +521,7 @@ INTERNAL void create_contacts(void)
}
} else {
/* Constraint ent no longer valid, delete entry */
contact_lookup_remove(&G.contact_lookup, entry);
entity_lookup_remove(&G.contact_lookup, entry);
entry = NULL;
}
}
@ -583,7 +582,7 @@ INTERNAL void create_contacts(void)
entity_enable_prop(constraint_ent, ENTITY_PROP_CONTACT_CONSTRAINT);
activate_now(constraint_ent);
ASSERT(!entry); /* Existing entry should never be present here */
contact_lookup_set(&G.contact_lookup, lookup_hash, constraint_ent->handle);
entity_lookup_set(&G.contact_lookup, key, constraint_ent->handle);
}
}
constraint = &constraint_ent->contact_constraint_data;
@ -648,21 +647,17 @@ INTERNAL void create_contacts(void)
/* TODO: Remove this (debugging) */
#if COLLIDER_DEBUG
{
struct string fdkey = STRING_FROM_BUFFER(BUFFER_FROM_STRUCT(&lookup_hash));
struct entity_handle *dbg_ent_handle = fixed_dict_get(&G.collision_debug_lookup.dict, fdkey);
if (!dbg_ent_handle) {
/* FIXME: Handle never released */
dbg_ent_handle = arena_push_zero(&G.collision_debug_lookup.arena, struct entity_handle);
struct entity *dbg_ent = entity_nil();
struct entity_lookup_entry *dbg_entry = entity_lookup_get(&G.collision_debug_lookup, key);
if (dbg_entry) {
dbg_ent = entity_from_handle(store, dbg_entry->entity);
}
struct entity *dbg_ent = entity_from_handle(store, *dbg_ent_handle);
if (!dbg_ent->valid) {
/* FIXME: Entity never released */
dbg_ent = entity_alloc(root);
entity_enable_prop(dbg_ent, ENTITY_PROP_COLLISION_DEBUG);
*dbg_ent_handle = dbg_ent->handle;
fixed_dict_set(&G.collision_debug_lookup.arena, &G.collision_debug_lookup.dict, fdkey, dbg_ent_handle);
entity_lookup_set(&G.collision_debug_lookup, key, dbg_ent->handle);
}
struct collision_debug *dbg = &dbg_ent->collision_debug_data;
@ -785,15 +780,53 @@ INTERNAL void prepare_contacts(void)
entity_disable_prop(constraint_ent, ENTITY_PROP_ACTIVE);
entity_enable_prop(constraint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME);
/* Remove from lookup */
u64 hash = contact_lookup_hash_from_entities(constraint->e0, constraint->e1);
struct contact_lookup_entry *entry = contact_lookup_get(&G.contact_lookup, hash);
struct entity_lookup_key key = entity_lookup_key_from_two_handles(constraint->e0, constraint->e1);
struct entity_lookup_entry *entry = entity_lookup_get(&G.contact_lookup, key);
if (entry) {
contact_lookup_remove(&G.contact_lookup, entry);
entity_lookup_remove(&G.contact_lookup, entry);
} else {
ASSERT(false); /* This should always exist */
}
}
}
#if COLLIDER_DEBUG
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *dbg_ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(dbg_ent)) continue;
if (!entity_has_prop(dbg_ent, ENTITY_PROP_COLLISION_DEBUG)) continue;
struct collision_debug *dbg = &dbg_ent->collision_debug_data;
struct entity *e0 = entity_from_handle(store, dbg->e0);
struct entity *e1 = entity_from_handle(store, dbg->e1);
if (!entity_is_valid_and_active(e0) || !entity_is_valid_and_active(e1)
|| !(entity_has_prop(e0, ENTITY_PROP_PHYSICAL_DYNAMIC) || entity_has_prop(e0, ENTITY_PROP_PHYSICAL_KINEMATIC))
|| !(entity_has_prop(e1, ENTITY_PROP_PHYSICAL_DYNAMIC) || entity_has_prop(e1, ENTITY_PROP_PHYSICAL_KINEMATIC))) {
/* Mark dbg ent for removal */
entity_disable_prop(dbg_ent, ENTITY_PROP_ACTIVE);
entity_enable_prop(dbg_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME);
/* Remove from lookup */
struct entity_lookup_key key = entity_lookup_key_from_two_handles(dbg->e0, dbg->e1);
struct entity_lookup_entry *entry = entity_lookup_get(&G.collision_debug_lookup, key);
if (e0->valid) {
--e0->colliding;
}
if (e1->valid) {
--e0->colliding;
}
if (entry) {
entity_lookup_remove(&G.collision_debug_lookup, entry);
} else {
ASSERT(false); /* This should always exist */
}
}
}
#endif
}
INTERNAL void warm_start_contacts(void)
@ -1563,7 +1596,6 @@ INTERNAL f32 determine_earliest_toi(f32 dt, f32 tolerance, u32 max_iterations)
struct entity_store *store = G.tick.entity_store;
//struct entity *root = G.root;
for (u64 e0_index = 0; e0_index < store->reserved; ++e0_index) {
struct entity *e0 = &store->entities[e0_index];
if (!entity_is_valid_and_active(e0)) continue;
@ -1583,7 +1615,6 @@ INTERNAL f32 determine_earliest_toi(f32 dt, f32 tolerance, u32 max_iterations)
e0_xf_t1 = xform_basis_rotated_world(e0_xf_t1, tick_angular_velocity);
}
/* Start e1 index at e0 index + 1 to prevent redundant checks */
for (u64 e1_index = e0_index + 1; e1_index < store->reserved; ++e1_index) {
struct entity *e1 = &store->entities[e1_index];
@ -1609,7 +1640,6 @@ INTERNAL f32 determine_earliest_toi(f32 dt, f32 tolerance, u32 max_iterations)
if (t != 0 && t < smallest_t) {
smallest_t = t;
}
}
}
@ -1617,24 +1647,6 @@ INTERNAL f32 determine_earliest_toi(f32 dt, f32 tolerance, u32 max_iterations)
}
/* ========================== *
* Update
@ -2254,11 +2266,21 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
f32 remaining_dt = dt;
integrate_velocities_from_forces(dt);
while (remaining_dt > 0) {
f32 earliest_toi = 1;
{
#if GAME_PHYSICS_ENABLE_TOI
const f32 min_toi = 0.000001f;
const f32 tolerance = 0.00001f;
const u32 max_iterations = 128;
f32 earliest_toi = max_f32(determine_earliest_toi(remaining_dt, tolerance, max_iterations), min_toi);
earliest_toi = max_f32(determine_earliest_toi(remaining_dt, tolerance, max_iterations), min_toi);
#else
(UNUSED)toi;
(UNUSED)determine_earliest_toi;
#endif
}
f32 step_dt = remaining_dt * earliest_toi;
create_contacts();
create_mouse_joints(game_cmds);