diff --git a/src/entity.h b/src/entity.h index e6a7e740..ed76c8d7 100644 --- a/src/entity.h +++ b/src/entity.h @@ -79,6 +79,7 @@ struct contact_point { }; struct contact_constraint { + u64 last_updated_tick; /* To avoid checking collisions for the same constraint twice in one tick */ struct entity_handle e0; struct entity_handle e1; f32 inv_m0; diff --git a/src/game.c b/src/game.c index 90b84f53..604cf4c6 100644 --- a/src/game.c +++ b/src/game.c @@ -12,6 +12,24 @@ #include "log.h" #include "collider.h" +struct contact_lookup_entry { + 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 contact_lookup { + struct arena arena; + struct contact_lookup_bucket buckets[4096]; + struct contact_lookup_entry *first_free_entry; +}; + GLOBAL struct { struct atomic_i32 game_thread_shutdown; struct sys_thread game_thread; @@ -37,6 +55,9 @@ GLOBAL struct { struct math_spring_result mouse_joint_angular_softness; f32 mouse_joint_max_force; + /* Bookkeeping structures */ + struct contact_lookup contact_lookup; + /* Ticks */ struct sys_mutex prev_tick_mutex; struct atomic_u64 prev_tick_id; @@ -143,6 +164,112 @@ INTERNAL void activate_now(struct entity *ent) ++ent->continuity_gen; } +/* ========================== * + * Contact lookup + * ========================== */ + +INTERNAL void contact_lookup_alloc(struct contact_lookup *l) +{ + MEMZERO_STRUCT(l); + l->arena = arena_alloc(GIGABYTE(64)); +} + +INTERNAL void contact_lookup_release(struct contact_lookup *l) +{ + arena_release(&l->arena); +} + +INTERNAL u64 contact_lookup_hash_from_entities(struct entity_handle h0, struct entity_handle h1) +{ + 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) { + res = e; + break; + } + } + return res; +} + +INTERNAL void contact_lookup_set(struct contact_lookup *l, u64 hash, struct entity_handle handle) +{ + u64 index = hash % ARRAY_COUNT(l->buckets); + struct contact_lookup_bucket *bucket = &l->buckets[index]; + + struct contact_lookup_entry *prev = NULL; + struct contact_lookup_entry **slot = &bucket->first; + while (*slot) { + if ((*slot)->hash == hash) { + break; + } + prev = *slot; + slot = &(*slot)->next; + } + + struct contact_lookup_entry *entry = *slot; + if (entry) { + /* Set existing entry */ + entry->contact_ent_handle = handle; + } else { + /* Allocate entry */ + if (l->first_free_entry) { + entry = l->first_free_entry; + l->first_free_entry->prev = NULL; + l->first_free_entry = entry->next; + } else { + entry = arena_push(&l->arena, struct contact_lookup_entry); + } + MEMZERO_STRUCT(entry); + + entry->hash = hash; + entry->contact_ent_handle = handle; + if (prev) { + entry->prev = prev; + prev->next = entry; + } + + bucket->last = entry; + *slot = entry; + } +} + +INTERNAL void contact_lookup_remove(struct contact_lookup *l, struct contact_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; + + if (prev) { + prev->next = next; + } else { + bucket->first = next; + } + + if (next) { + next->prev = prev; + } else { + bucket->last = prev; + } + + if (l->first_free_entry) { + l->first_free_entry->prev = entry; + } + entry->next = l->first_free_entry; + entry->prev = NULL; + l->first_free_entry = entry; +} + /* ========================== * * Reset * ========================== */ @@ -152,7 +279,11 @@ INTERNAL void reset_world(void) if (G.tick.entity_store) { /* Release world */ world_release(&G.tick); + /* Release bookkeeping */ + contact_lookup_release(&G.contact_lookup); } + /* Create bookkeeping */ + contact_lookup_alloc(&G.contact_lookup); /* Re-create world */ world_alloc(&G.tick); G.tick.continuity_gen = atomic_u64_eval(&G.prev_tick_continuity_gen) + 1; @@ -225,7 +356,6 @@ INTERNAL void spawn_test_entities(f32 offset) entity_set_xform(e, xf); - e->linear_ground_friction = 250;e->angular_ground_friction = 200; //e->control_force = 500; @@ -415,126 +545,109 @@ INTERNAL void spawn_test_entities(f32 offset) INTERNAL void create_contacts(void) { - /* TODO: Remove this */ - /* FIXME: Dict has leaks: Entries never removed, even when constraint is no longer valid, or either entities are no longer valid. */ - static u64 constraint_iteration = 0; - ++constraint_iteration; - static struct arena dict_arena = ZI; - static struct fixed_dict dict = ZI; - if (dict.buckets_count == 0) { - dict_arena = arena_alloc(GIGABYTE(64)); - dict = fixed_dict_init(&dict_arena, 4096); - } - - /* FIXME: I think it's technically possible for constraint entities to swap between 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; - if (!entity_has_prop(e0, ENTITY_PROP_PHYSICAL)) continue; - if (e0->local_collider.count <= 0) continue; + for (u64 check0_index = 0; check0_index < store->reserved; ++check0_index) { + struct entity *check0 = &store->entities[check0_index]; + if (!entity_is_valid_and_active(check0)) continue; + if (!entity_has_prop(check0, ENTITY_PROP_PHYSICAL)) continue; + if (check0->local_collider.count <= 0) continue; - struct xform e0_xf = entity_get_xform(e0); - struct collider_shape e0_collider = e0->local_collider; + struct xform check0_xf = entity_get_xform(check0); + struct collider_shape check0_collider = check0->local_collider; - for (u64 e1_index = 0; e1_index < store->reserved; ++e1_index) { - struct entity *e1 = &store->entities[e1_index]; - if (e1 == e0) continue; - if (!entity_is_valid_and_active(e1)) continue; - if (!entity_has_prop(e1, ENTITY_PROP_PHYSICAL)) continue; - if (e1->local_collider.count <= 0) continue; + for (u64 check1_index = 0; check1_index < store->reserved; ++check1_index) { + struct entity *check1 = &store->entities[check1_index]; + if (check1 == check0) continue; + if (!entity_is_valid_and_active(check1)) continue; + if (!entity_has_prop(check1, ENTITY_PROP_PHYSICAL)) continue; + if (check1->local_collider.count <= 0) continue; - /* TODO: Remove this (temporary stop to prevent double-constraint creation) */ - if (e0_index >= e1_index) { - continue; + /* Deterministic entity order based on index */ + struct entity *e0; + struct entity *e1; + struct xform e0_xf; + struct xform e1_xf; + struct collider_shape e0_collider; + struct collider_shape e1_collider; + if (check0_index < check1_index) { + e0 = check0; + e1 = check1; + e0_xf = check0_xf; + e1_xf = entity_get_xform(check1); + e0_collider = check0_collider; + e1_collider = check1->local_collider; + } else { + e0 = check1; + e1 = check0; + e0_xf = entity_get_xform(check1); + e1_xf = check0_xf; + e0_collider = check1->local_collider; + e1_collider = check0_collider; } - u64 constraint_hash; - struct string constraint_key; - struct entity_handle *entry; - struct entity *constraint_ent = NULL; - { - { - struct entity_handle h0 = e0->handle; - struct entity_handle h1 = e1->handle; - constraint_hash = hash_fnv64(HASH_FNV64_BASIS, BUFFER_FROM_STRUCT(&h0)); - constraint_hash = hash_fnv64(constraint_hash, BUFFER_FROM_STRUCT(&h1)); - constraint_key = STRING_FROM_BUFFER(BUFFER_FROM_STRUCT(&constraint_hash)); - } + 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); - entry = fixed_dict_get(&dict, constraint_key); - if (entry) { - struct entity *t = entity_from_handle(store, *entry); - if (entity_is_valid_and_active(t)) { - if (t->contact_constraint_data.last_iteration == constraint_iteration) { - /* Constraint has already been computed this iteration */ - continue; - } else { - t->contact_constraint_data.last_iteration = constraint_iteration; - constraint_ent = t; - } - } else { - /* Constraint entity no longer valid */ - fixed_dict_set(&dict_arena, &dict, constraint_key, NULL); + struct entity *constraint_ent = entity_nil(); + if (entry) { + constraint_ent = entity_from_handle(store, entry->contact_ent_handle); + 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 */ continue; + } else { + ++constraint_ent->contact_constraint_data.last_iteration; } + } else { + /* Constraint ent no longer valid, delete entry */ + contact_lookup_remove(&G.contact_lookup, entry); + entry = NULL; } } /* Calculate collision */ - struct xform e1_xf = entity_get_xform(e1); - struct collider_collision_points_result res = collider_collision_points(&e0_collider, &e1->local_collider, e0_xf, e1_xf); + struct collider_collision_points_result collider_res = collider_collision_points(&e0_collider, &e1_collider, e0_xf, e1_xf); /* Parts of algorithm are hard-coded to support 2 contact points */ CT_ASSERT(ARRAY_COUNT(constraint_ent->contact_constraint_data.points) == 2); - CT_ASSERT(ARRAY_COUNT(res.points) == 2); + CT_ASSERT(ARRAY_COUNT(collider_res.points) == 2); - /* TODO: Move this down */ - if (res.num_points > 0 || COLLIDER_DEBUG) { - if (!constraint_ent) { + if (collider_res.num_points > 0) { + if (!entity_is_valid_and_active(constraint_ent)) { constraint_ent = entity_alloc(root); constraint_ent->contact_constraint_data.e1 = e1->handle; constraint_ent->contact_constraint_data.e0 = e0->handle; /* TODO: Should we recalculate normal as more contact points are added? */ entity_enable_prop(constraint_ent, ENTITY_PROP_CONTACT_CONSTRAINT); activate_now(constraint_ent); - if (entry) { - *entry = constraint_ent->handle; - } else { - entry = arena_push(&dict_arena, struct entity_handle); - *entry = constraint_ent->handle; - fixed_dict_set(&dict_arena, &dict, constraint_key, entry); + if (!entry) { + contact_lookup_set(&G.contact_lookup, lookup_hash, constraint_ent->handle); } } struct contact_constraint *constraint = &constraint_ent->contact_constraint_data; - constraint->normal = res.normal; + constraint->normal = collider_res.normal; +#if 0 /* TODO: Remove this (debugging) */ -#if COLLIDER_DEBUG { - constraint->res = res; + constraint->res = collider_res; constraint->dbg_xf0 = e0_xf; constraint->dbg_xf1 = e1_xf; if (constraint->num_points == 0) { - if (res.num_points > 0) { + if (collider_res.num_points > 0) { ++e0->colliding; ++e1->colliding; } } else { - if (res.num_points == 0) { + if (collider_res.num_points == 0) { --e0->colliding; --e1->colliding; } } } #endif - } - - if (res.num_points > 0) { - struct contact_constraint *constraint = &constraint_ent->contact_constraint_data; constraint->friction = math_sqrt(e0->friction * e1->friction); @@ -543,8 +656,8 @@ INTERNAL void create_contacts(void) struct contact_point *old = &constraint->points[i]; u32 id = old->id; b32 found = false; - for (u32 j = 0; j < res.num_points; ++j) { - if (res.points[j].id == id) { + for (u32 j = 0; j < collider_res.num_points; ++j) { + if (collider_res.points[j].id == id) { found = true; break; } @@ -557,8 +670,8 @@ INTERNAL void create_contacts(void) } /* Update / insert returned contacts */ - for (u32 i = 0; i < res.num_points; ++i) { - struct collider_collision_point *res_point = &res.points[i]; + for (u32 i = 0; i < collider_res.num_points; ++i) { + struct collider_collision_point *res_point = &collider_res.points[i]; struct v2 point = res_point->point; f32 sep = res_point->separation; u32 id = res_point->id; @@ -589,9 +702,7 @@ INTERNAL void create_contacts(void) contact->dbg_pt = point; #endif } - - - } else if (constraint_ent) { + } else if (constraint_ent->valid) { constraint_ent->contact_constraint_data.num_points = 0; } } @@ -665,6 +776,10 @@ INTERNAL void prepare_contacts(void) contact->tangent_impulse = 0; #endif } + } else { + /* Mark constraint for removal */ + entity_disable_prop(constraint_ent, ENTITY_PROP_ACTIVE); + entity_enable_prop(constraint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME); } } } @@ -903,6 +1018,10 @@ INTERNAL void prepare_motor_joints(void) joint->linear_impulse = V2(0, 0); joint->angular_impulse = 0; #endif + } else { + /* Mark joint for removal */ + entity_disable_prop(joint_ent, ENTITY_PROP_ACTIVE); + entity_enable_prop(joint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME); } } } @@ -1013,10 +1132,6 @@ INTERNAL void solve_motor_joints(f32 dt) } } - - - - #if 1 /* ========================== * @@ -1049,9 +1164,8 @@ INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds) struct v2 cursor = G.user_cursor; - /* TODO: Remove this */ - static struct entity_handle target_ent_handle = ZI; - static struct entity_handle joint_ent_handle = ZI; + struct entity *joint_ent = entity_find_first_match_one(store, ENTITY_PROP_MOUSE_JOINT); + struct entity *target_ent = entity_from_handle(store, joint_ent->mouse_joint_data.target); if (start_dragging) { struct xform mouse_xf = xform_from_pos(cursor); @@ -1067,22 +1181,18 @@ INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds) struct collider_shape ent_collider = ent->local_collider; if (ent_collider.count > 0) { struct xform ent_xf = entity_get_xform(ent); - /* TODO: Can just use boolean GJK */ struct collider_collision_points_result res = collider_collision_points(&ent_collider, &mouse_shape, ent_xf, mouse_xf); if (res.num_points > 0) { - target_ent_handle = ent->handle; + target_ent = ent; break; } } } } else if (stop_dragging) { - target_ent_handle = entity_nil_handle(); + target_ent = entity_nil(); } - struct entity *target_ent = entity_from_handle(store, target_ent_handle); - struct entity *joint_ent = entity_from_handle(store, joint_ent_handle); - if (entity_is_valid_and_active(target_ent)) { if (!entity_is_valid_and_active(joint_ent)) { joint_ent = entity_alloc(root); @@ -1091,16 +1201,15 @@ INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds) entity_enable_prop(joint_ent, ENTITY_PROP_PHYSICAL); entity_enable_prop(joint_ent, ENTITY_PROP_MOUSE_JOINT); entity_enable_prop(joint_ent, ENTITY_PROP_ACTIVE); - joint_ent_handle = joint_ent->handle; } struct mouse_joint *joint = &joint_ent->mouse_joint_data; struct xform xf = entity_get_xform(target_ent); f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); - if (!entity_handle_eq(joint->target, target_ent_handle)) { + if (!entity_handle_eq(joint->target, target_ent->handle)) { joint->point_local_start = xform_invert_mul_v2(xf, cursor); - joint->target = target_ent_handle; + joint->target = target_ent->handle; } joint->point_local_end = xform_invert_mul_v2(xf, cursor); @@ -1152,8 +1261,11 @@ INTERNAL void prepare_mouse_joints(void) joint->linear_impulse = V2(0, 0); joint->angular_impulse = 0; #endif + } else { + /* Mark joint for removal */ + entity_disable_prop(joint_ent, ENTITY_PROP_ACTIVE); + entity_enable_prop(joint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME); } - } } @@ -2176,6 +2288,18 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) for (u64 i = 0; i < ents_to_release_count; ++i) { struct entity *ent = ents_to_release[i]; if (ent->valid) { + /* Remove contact constraint from lookup */ + if (entity_has_prop(ent, ENTITY_PROP_CONTACT_CONSTRAINT)) { + u64 hash = contact_lookup_hash_from_entities(ent->contact_constraint_data.e0, ent->contact_constraint_data.e1); + struct contact_lookup_entry *entry = contact_lookup_get(&G.contact_lookup, hash); + if (entry) { + contact_lookup_remove(&G.contact_lookup, entry); + } else { + ASSERT(false); /* This should always exist */ + } + } + + /* Release */ entity_release(store, ent); } }