cross-chunk tile wall merging

This commit is contained in:
jacob 2025-05-21 21:17:04 -05:00
parent 4bc6ca6679
commit a47009f16b
6 changed files with 239 additions and 169 deletions

View File

@ -39,7 +39,7 @@
#define SPACE_CELL_BINS_SQRT (64)
#define SPACE_CELL_SIZE (1)
#define SIM_TILES_PER_UNIT_SQRT (2)
#define SIM_TILES_PER_UNIT_SQRT (4)
#define SIM_TILES_PER_CHUNK_SQRT (16)
#define SIM_TICKS_PER_SECOND 50

View File

@ -13,6 +13,15 @@
* Contact
* ========================== */
INTERNAL b32 can_contact(struct sim_ent *e0, struct sim_ent *e1)
{
b32 res = false;
res = e0 != e1 &&
!sim_ent_id_eq(e0->top, e1->top) &&
!(sim_ent_has_prop(e0, SEPROP_WALL) && sim_ent_has_prop(e1, SEPROP_WALL));
return res;
}
void phys_create_and_update_contacts(struct phys_step_ctx *ctx, f32 elapsed_dt, u64 phys_iteration)
{
__prof;
@ -39,10 +48,10 @@ void phys_create_and_update_contacts(struct phys_step_ctx *ctx, f32 elapsed_dt,
struct space_entry *space_entry;
while ((space_entry = space_iter_next(&iter))) {
struct sim_ent *check1 = sim_ent_from_id(ss, space_entry->ent);
if (check1 == check0) continue;
if (!sim_ent_is_valid_and_active(check1)) continue;
if (!(sim_ent_has_prop(check1, SEPROP_SOLID) || sim_ent_has_prop(check1, SEPROP_SENSOR))) continue;
if (check1->local_collider.count <= 0) continue;
if (!can_contact(check0, check1)) continue;
/* Deterministic order based on entity id */
struct sim_ent *e0;
@ -1171,10 +1180,10 @@ f32 phys_determine_earliest_toi(struct phys_step_ctx *ctx, f32 step_dt, f32 tole
struct space_entry *entry;
while ((entry = space_iter_next(&iter))) {
struct sim_ent *e1 = sim_ent_from_id(ss, entry->ent);
if (e1 == e0) continue;
if (!sim_ent_should_simulate(e1)) continue;
if (!(sim_ent_has_prop(e1, SEPROP_SOLID) || sim_ent_has_prop(e1, SEPROP_SENSOR))) continue;
if (e1->local_collider.count <= 0) continue;
if (!can_contact(e0, e1)) continue;
struct collider_shape e1_collider = e1->local_collider;
struct xform e1_xf_t0 = sim_ent_get_xform(e1);

View File

@ -377,21 +377,21 @@ INTERNAL void test_spawn_tile(struct sim_snapshot *world, struct v2 world_pos)
INTERNAL SORT_COMPARE_FUNC_DEF(tile_chunk_sort, arg_a, arg_b, udata)
{
(UNUSED)udata;
struct sim_ent *a = (struct sim_ent *)arg_a;
struct sim_ent *b = (struct sim_ent *)arg_b;
struct sim_ent *a = *(struct sim_ent **)arg_a;
struct sim_ent *b = *(struct sim_ent **)arg_b;
struct v2i32 index_a = a->tile_chunk_index;
struct v2i32 index_b = b->tile_chunk_index;
i32 res = 0;
res = 2 * ((index_a.y > index_b.y) - (index_a.y < index_b.y)) + ((index_a.x > index_b.x) - (index_a.x < index_b.x));
res = 2 * ((index_a.y < index_b.y) - (index_a.y > index_b.y)) + ((index_a.x < index_b.x) - (index_a.x > index_b.x));
return res;
}
INTERNAL void test_generate_walls(struct sim_ent *parent)
INTERNAL void test_generate_walls(struct sim_snapshot *world)
{
#if 1
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
struct sim_snapshot *world = parent->ss;
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
/* Release existing walls */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
@ -403,17 +403,17 @@ INTERNAL void test_generate_walls(struct sim_ent *parent)
}
/* Sort tile chunks */
struct sim_ent **tile_chunks = arena_dry_push(scratch.arena, struct sim_ent *);
u64 tile_chunks_count = 0;
struct sim_ent **sorted_tile_chunks = arena_dry_push(scratch.arena, struct sim_ent *);
u64 sorted_tile_chunks_count = 0;
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *ent = &world->ents[ent_index];
if (!ent->valid) continue;
if (sim_ent_has_prop(ent, SEPROP_TILE_CHUNK)) {
*arena_push_no_zero(scratch.arena, struct sim_ent *) = ent;
++tile_chunks_count;
++sorted_tile_chunks_count;
}
}
merge_sort(tile_chunks, tile_chunks_count, sizeof(*tile_chunks), tile_chunk_sort, NULL);
merge_sort(sorted_tile_chunks, sorted_tile_chunks_count, sizeof(*sorted_tile_chunks), tile_chunk_sort, NULL);
struct wall_node {
struct v2i32 start;
@ -421,13 +421,16 @@ INTERNAL void test_generate_walls(struct sim_ent *parent)
struct wall_node *next;
};
/* Dicts containing walls that end on edge of tile chunk, keyed by tile end index.
* Used to expand walls accross tile chunks. */
struct dict horizontal_ends_dict = dict_init(scratch.arena, 1024);
struct dict vertical_ends_dict = dict_init(scratch.arena, 1024);
struct wall_node *first_wall = NULL;
/* Generate horizontal wall nodes */
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
struct sim_ent *chunk = &world->ents[ent_index];
if (!chunk->valid) continue;
if (sim_ent_has_prop(chunk, SEPROP_TILE_CHUNK)) {
for (u64 sorted_index = 0; sorted_index < sorted_tile_chunks_count; ++sorted_index) {
struct sim_ent *chunk = sorted_tile_chunks[sorted_index];
struct v2i32 chunk_index = chunk->tile_chunk_index;
/* Generate horizontal wall nodes */
{
@ -474,11 +477,26 @@ INTERNAL void test_generate_walls(struct sim_ent *parent)
/* Stop wall */
struct v2i32 start = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(wall_start, tile_y));
struct v2i32 end = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(wall_end, tile_y));
struct wall_node *node = arena_push(scratch.arena, struct wall_node);
struct wall_node *node = NULL;
if (wall_start == 0) {
u64 start_hash = rand_u64_from_seed(*(u64 *)&start);
struct dict_entry *entry = dict_get_entry(&horizontal_ends_dict, start_hash);
if (entry) {
node = (struct wall_node *)entry->value;
dict_remove_entry(&horizontal_ends_dict, entry);
}
}
if (!node) {
node = arena_push(scratch.arena, struct wall_node);
node->start = start;
node->end = end;
node->next = first_wall;
first_wall = node;
}
node->end = end;
if (wall_end == SIM_TILES_PER_CHUNK_SQRT) {
u64 end_hash = rand_u64_from_seed(*(u64 *)&end);
dict_set(scratch.arena, &horizontal_ends_dict, end_hash, node);
}
wall_start = -1;
wall_end = -1;
}
@ -531,11 +549,26 @@ INTERNAL void test_generate_walls(struct sim_ent *parent)
/* Stop wall */
struct v2i32 start = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(tile_x, wall_start));
struct v2i32 end = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(tile_x, wall_end));
struct wall_node *node = arena_push(scratch.arena, struct wall_node);
struct wall_node *node = NULL;
if (wall_start == 0) {
u64 start_hash = rand_u64_from_seed(*(u64 *)&start);
struct dict_entry *entry = dict_get_entry(&vertical_ends_dict, start_hash);
if (entry) {
node = (struct wall_node *)entry->value;
dict_remove_entry(&vertical_ends_dict, entry);
}
}
if (!node) {
node = arena_push(scratch.arena, struct wall_node);
node->start = start;
node->end = end;
node->next = first_wall;
first_wall = node;
}
node->end = end;
if (wall_end == SIM_TILES_PER_CHUNK_SQRT) {
u64 end_hash = rand_u64_from_seed(*(u64 *)&end);
dict_set(scratch.arena, &vertical_ends_dict, end_hash, node);
}
wall_start = -1;
wall_end = -1;
}
@ -543,21 +576,12 @@ INTERNAL void test_generate_walls(struct sim_ent *parent)
}
}
}
}
/* Create wall entities */
for (struct wall_node *node = first_wall; node; node = node->next) {
struct sim_ent *wall_ent = sim_ent_alloc_sync_src(parent);
struct sim_ent *wall_ent = sim_ent_alloc_sync_src(root);
sim_ent_enable_prop(wall_ent, SEPROP_WALL);
#if 0
struct v2i32 node_start = node->start;
struct v2i32 node_end = node->end;
struct v2 start = V2(node_start.x / SIM_TILES_PER_UNIT_SQRT, node_start.y / SIM_TILES_PER_UNIT_SQRT);
struct v2 end = V2(node_end.x / SIM_TILES_PER_UNIT_SQRT, node_end.y / SIM_TILES_PER_UNIT_SQRT);
#endif
struct v2 start = sim_pos_from_world_tile_index(node->start);
struct v2 end = sim_pos_from_world_tile_index(node->end);
@ -567,12 +591,11 @@ INTERNAL void test_generate_walls(struct sim_ent *parent)
sim_ent_enable_prop(wall_ent, SEPROP_SOLID);
wall_ent->local_collider.count = 2;
wall_ent->local_collider.points[1] = v2_sub(end, start);
sim_ent_activate(wall_ent, world->tick);
}
scratch_end(scratch);
#else
(UNUSED)parent;
#endif
}
@ -774,7 +797,7 @@ void sim_step(struct sim_step_ctx *ctx)
player->player_client_handle = client->handle;
sim_ent_enable_prop(player, SEPROP_PLAYER);
player->predictor = player->id;
sim_ent_enable_prop(player, SEPROP_ACTIVE);
sim_ent_activate(player, world->tick);
client->player_id = player->id;
if (client == user_input_client) {
user_input_client->player_id = player->id;
@ -949,7 +972,7 @@ void sim_step(struct sim_step_ctx *ctx)
}
}
if (flags & SIM_CONTROL_FLAG_WALLS_TEST) {
test_generate_walls(root);
test_generate_walls(world);
}
if (flags & SIM_CONTROL_FLAG_EXPLODE_TEST) {
logf_info("Explosion test");
@ -958,37 +981,9 @@ void sim_step(struct sim_step_ctx *ctx)
}
if (flags & SIM_CONTROL_FLAG_TILE_TEST) {
#if 0
if (is_master) {
struct v2 cursor_pos = player->player_cursor_pos;
/* FIXME: Negative indices */
struct v2i32 tile_pos = V2I32(cursor_pos.x * SIM_TILES_PER_UNIT_SQRT, cursor_pos.y * SIM_TILES_PER_UNIT_SQRT);
struct v2i32 chunk_pos = V2I32(tile_pos.x * SIM_TILES_PER_CHUNK_SQRT, tile_pos.y * SIM_TILES_PER_CHUNK_SQRT);
struct v2i32 tile_pos_in_chunk = V2I32(tile_pos.x % SIM_TILES_PER_CHUNK_SQRT, tile_pos.y % SIM_TILES_PER_CHUNK_SQRT);
struct sim_ent_id chunk_id = sim_ent_chunk_id_from_chunk_pos(player->id, chunk_pos);
struct sim_ent *chunk_ent = sim_ent_from_id(world, chunk_id);
if (!chunk_ent->valid) {
chunk_ent = sim_ent_alloc_sync_src_with_id(root, chunk_id);
sim_ent_enable_prop(chunk_ent, SEPROP_TILE_CHUNK);
}
struct string old_data = sim_ent_get_tile_chunk_data(chunk_ent);
struct string new_data = ZI;
new_data.len = SIM_TILES_PER_CHUNK_SQRT * SIM_TILES_PER_CHUNK_SQRT;
new_data.text = arena_push_array_no_zero(scratch.arena, u8, new_data.len);
if (old_data.len == new_data.len) {
MEMCPY(new_data.text, old_data.text, new_data.len);
} else {
MEMZERO(new_data.text, new_data.len);
}
u64 tile_index = tile_pos_in_chunk.x + (tile_pos_in_chunk.y * SIM_TILES_PER_CHUNK_SQRT);
new_data.text[tile_index] = SIM_TILE_KIND_TEST;
sim_ent_set_tile_chunk_data(chunk_ent, new_data);
}
#else
test_spawn_tile(world, player->player_cursor_pos);
#endif
} else if (old_control.flags & SIM_CONTROL_FLAG_TILE_TEST) {
test_generate_walls(world);
}
}
} break;

View File

@ -162,7 +162,7 @@ struct tar_archive tar_parse(struct arena *arena, struct string data, struct str
return archive;
}
struct tar_entry *tar_get(const struct tar_archive *archive, struct string name)
struct tar_entry *tar_get(struct tar_archive *archive, struct string name)
{
u64 hash = hash_fnv64(HASH_FNV64_BASIS, name);
return dict_get(&archive->lookup, hash);

View File

@ -18,6 +18,6 @@ struct tar_archive {
};
struct tar_archive tar_parse(struct arena *arena, struct string data, struct string prefix);
struct tar_entry *tar_get(const struct tar_archive *archive, struct string name);
struct tar_entry *tar_get(struct tar_archive *archive, struct string name);
#endif

View File

@ -35,9 +35,9 @@ INLINE u64 hash_fnv64(u64 seed, struct string s)
* ========================== */
/* Compare functions should
* return an int < 0 if a < b
* return an int = 0 if a == b
* return an int > 0 if a > b
* return a positive value if a should go before b
* return a negative value if a should go after b
* return 0 otherwise
*/
#define SORT_COMPARE_FUNC_DEF(name, arg_a, arg_b, arg_udata) i32 name(void *arg_a, void *arg_b, void *arg_udata)
typedef SORT_COMPARE_FUNC_DEF(sort_compare_func, a, b, udata);
@ -108,18 +108,23 @@ INLINE void merge_sort(void *items, u64 item_count, u64 item_size, sort_compare_
struct dict_entry {
u64 hash;
u64 value;
struct dict_entry *prev_in_bin;
struct dict_entry *next_in_bin;
struct dict_entry *prev;
struct dict_entry *next;
};
struct dict_bin {
struct dict_entry *first;
struct dict_entry *last;
};
struct dict {
u64 bins_count;
struct dict_bin *bins;
struct dict_entry *first_free;
struct dict_entry *first;
struct dict_entry *last;
};
INLINE struct dict dict_init(struct arena *arena, u64 bins_count)
@ -131,10 +136,9 @@ INLINE struct dict dict_init(struct arena *arena, u64 bins_count)
return dict;
}
INLINE void dict_set(struct arena *arena, struct dict *dict, u64 hash, u64 value)
INLINE struct dict_entry *dict_ensure_entry(struct arena *arena, struct dict *dict, u64 hash)
{
__prof;
struct dict_bin *bin = &dict->bins[hash % dict->bins_count];
struct dict_entry *entry = bin->first;
@ -148,19 +152,41 @@ INLINE void dict_set(struct arena *arena, struct dict *dict, u64 hash, u64 value
/* No match found, create new entry */
if (!entry) {
entry = arena_push(arena, struct dict_entry);
entry->value = value;
if (dict->first_free) {
entry = dict->first_free;
dict->first_free = entry->next;
} else {
entry = arena_push_no_zero(arena, struct dict_entry);
}
MEMZERO_STRUCT(entry);
entry->hash = hash;
entry->next_in_bin = bin->first;
entry->next = dict->first;
dict->first = entry;
if (bin->last) {
bin->last->next_in_bin = entry;
entry->prev_in_bin = bin->last;
} else {
bin->first = entry;
}
bin->last = entry;
if (dict->last) {
dict->last->next = entry;
entry->prev = dict->last;
} else {
dict->first = entry;
}
dict->last = entry;
}
return entry;
}
INLINE void dict_set(struct arena *arena, struct dict *dict, u64 hash, u64 value)
{
__prof;
struct dict_entry *entry = dict_ensure_entry(arena, dict, hash);
entry->value = value;
}
INLINE struct dict_entry *dict_get_entry(const struct dict *dict, u64 hash)
INLINE struct dict_entry *dict_get_entry(struct dict *dict, u64 hash)
{
__prof;
struct dict_entry *result = NULL;
@ -175,13 +201,53 @@ INLINE struct dict_entry *dict_get_entry(const struct dict *dict, u64 hash)
return result;
}
INLINE u64 dict_get(const struct dict *dict, u64 hash)
INLINE u64 dict_get(struct dict *dict, u64 hash)
{
__prof;
struct dict_entry *entry = dict_get_entry(dict, hash);
return entry ? entry->value : 0;
}
INLINE void dict_remove_entry(struct dict *dict, struct dict_entry *entry)
{
/* Remove from bin */
{
struct dict_bin *bin = &dict->bins[entry->hash % dict->bins_count];
struct dict_entry *prev_in_bin = entry->prev_in_bin;
struct dict_entry *next_in_bin = entry->next_in_bin;
if (prev_in_bin) {
prev_in_bin->next_in_bin = next_in_bin;
} else {
bin->first = next_in_bin;
}
if (next_in_bin) {
next_in_bin->prev_in_bin = prev_in_bin;
} else {
bin->last = prev_in_bin;
}
}
/* Remove from list */
{
struct dict_entry *prev = entry->prev;
struct dict_entry *next = entry->next;
if (prev) {
prev->next = next;
} else {
dict->first = next;
}
if (next) {
next->prev = prev;
} else {
dict->last = prev;
}
}
/* Insert into free list */
{
entry->next = dict->first_free;
dict->first_free = entry;
}
}
/* ========================== *
* Sync flag
* ========================== */