diff --git a/src/config.h b/src/config.h index 97f1ecda..871621cb 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ #define PIXELS_PER_UNIT 256.0 #define GAME_FPS 50.0 -#define GAME_TIMESCALE 1 +#define GAME_TIMESCALE 1.0 /* How many ticks back in time should the user blend between? * = * diff --git a/src/entity.c b/src/entity.c index dd92b92d..5c373bf6 100644 --- a/src/entity.c +++ b/src/entity.c @@ -72,7 +72,8 @@ void entity_store_copy_replace(struct entity_store *dest, struct entity_store *s void entity_store_reset(struct entity_store *store) { - store->count = 0; + store->allocated = 0; + store->reserved = 0; store->first_free = entity_nil_handle(); arena_pop_to(&store->arena, STORE_ENTITIES_OFFSET); store_make_root(store); @@ -87,7 +88,7 @@ INTERNAL struct entity *entity_alloc_internal(struct entity_store *store) struct entity *entity = NULL; struct entity_handle handle = { 0 }; if (store->first_free.gen) { - /* Reuse from free list */ + /* Reuse from free list */; entity = entity_from_handle(store, store->first_free); handle = entity->handle; ++handle.gen; @@ -95,10 +96,11 @@ INTERNAL struct entity *entity_alloc_internal(struct entity_store *store) } else { /* Make new */ entity = arena_push(&store->arena, struct entity); - handle = (struct entity_handle) { .gen = 1, .idx = store->count++ }; + handle = (struct entity_handle) { .gen = 1, .idx = store->reserved++ }; } *entity = g_entity_default; entity->handle = handle; + ++store->allocated; return entity; } @@ -125,6 +127,7 @@ INTERNAL void entity_release_internal(struct entity_store *store, struct entity ent->valid = false; ent->next_free = store->first_free; store->first_free = ent->handle; + --store->allocated; } void entity_release(struct entity_store *store, struct entity *ent) @@ -152,7 +155,7 @@ struct entity_store *entity_get_store(struct entity *ent) /* Returns a valid entity or read-only nil entity. Always safe to read result, need to check `valid` to write. */ struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle) { - if (handle.gen != 0 && handle.idx < store->count) { + if (handle.gen != 0 && handle.idx < store->reserved) { struct entity *entity = &store->entities[handle.idx]; if (entity->handle.gen == handle.gen) { return entity; @@ -163,7 +166,7 @@ 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) { - u64 count = store->count; + u64 count = store->reserved; struct entity *entities = store->entities; for (u64 entity_index = 0; entity_index < count; ++entity_index) { struct entity *ent = &entities[entity_index]; @@ -176,7 +179,7 @@ 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) { - u64 count = store->count; + u64 count = store->reserved; struct entity *entities = store->entities; for (u64 entity_index = 0; entity_index < count; ++entity_index) { struct entity *ent = &entities[entity_index]; diff --git a/src/entity.h b/src/entity.h index d41cb600..0c75e344 100644 --- a/src/entity.h +++ b/src/entity.h @@ -34,7 +34,8 @@ struct entity_handle { struct entity_store { struct arena arena; - u64 count; + u64 allocated; + u64 reserved; struct entity_handle first_free; struct entity_handle root; struct entity *entities; diff --git a/src/game.c b/src/game.c index b3b2652b..5fd5861c 100644 --- a/src/game.c +++ b/src/game.c @@ -247,10 +247,40 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } /* ========================== * - * Update sprite from animation + * Activate entities * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { + struct entity *ent = &store->entities[entity_index]; + if (!ent->valid) continue; + + if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) { + entity_enable_prop(ent, ENTITY_PROP_ACTIVE); + ++ent->continuity_gen; + } + } + + /* ========================== * + * Reset triggered entities + * ========================== */ + + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { + struct entity *ent = &store->entities[entity_index]; + if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; + + if (entity_has_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK)) { + entity_disable_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK); + entity_enable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK); + } else if (entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) { + entity_disable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK); + } + } + + /* ========================== * + * Update animations from sprite + * ========================== */ + + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (sprite_tag_is_nil(ent->sprite)) continue; @@ -302,7 +332,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) * Update control from player cmds * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; @@ -363,7 +393,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) * Test * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; @@ -394,67 +424,11 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } } - /* ========================== * - * Activate triggers - * ========================== */ - - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { - struct entity *ent = &store->entities[entity_index]; - if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; - - /* Trigger equipped */ - if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) { - struct entity *eq = entity_from_handle(store, ent->equipped); - if (entity_has_prop(eq, ENTITY_PROP_ACTIVE)) { - entity_enable_prop(eq, ENTITY_PROP_TRIGGERED_THIS_TICK); - } - } - - } - - /* ========================== * - * Fire triggers - * ========================== */ - - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { - struct entity *ent = &store->entities[entity_index]; - if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; - if (!entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) continue; - if ((time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue; - - ent->last_triggered = time; - - /* Fire weapon */ - if (entity_has_prop(ent, ENTITY_PROP_WEAPON)) { - struct xform xf = entity_get_xform(ent); - - struct sprite_tag sprite = ent->sprite; - u32 animation_frame = ent->animation_frame; - struct xform sprite_xform = xform_mul(xf, ent->sprite_local_xform); - struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite); - - struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, STR("out"), animation_frame); - struct v2 out_pos = xform_mul_v2(sprite_xform, out_slice.center); - struct v2 out_vec = xform_basis_mul_v2(sprite_xform, out_slice.dir); - out_vec = v2_norm(out_vec); - out_vec = v2_mul(out_vec, 2); - - { - /* Spawn bullet */ - struct entity *bullet = entity_alloc(root); - bullet->sprite = sprite_tag_from_path(STR("res/graphics/bullet.ase")); - struct xform bullet_xf = XFORM_POS(out_pos); - entity_set_xform(bullet, bullet_xf); - bullet->velocity = out_vec; - } - } - } - /* ========================== * * Simulate entity physics * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; @@ -540,11 +514,67 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } } + /* ========================== * + * Activate triggers + * ========================== */ + + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { + struct entity *ent = &store->entities[entity_index]; + if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; + + /* Trigger equipped */ + if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) { + struct entity *eq = entity_from_handle(store, ent->equipped); + if (entity_has_prop(eq, ENTITY_PROP_ACTIVE)) { + entity_enable_prop(eq, ENTITY_PROP_TRIGGERED_THIS_TICK); + } + } + + } + + /* ========================== * + * Fire triggers + * ========================== */ + + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { + struct entity *ent = &store->entities[entity_index]; + if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; + if (!entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) continue; + if ((time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue; + + ent->last_triggered = time; + + /* Fire weapon */ + if (entity_has_prop(ent, ENTITY_PROP_WEAPON)) { + struct xform xf = entity_get_xform(ent); + + struct sprite_tag sprite = ent->sprite; + u32 animation_frame = ent->animation_frame; + struct xform sprite_xform = xform_mul(xf, ent->sprite_local_xform); + struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite); + + struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, STR("out"), animation_frame); + struct v2 out_pos = xform_mul_v2(sprite_xform, out_slice.center); + struct v2 out_vec = xform_basis_mul_v2(sprite_xform, out_slice.dir); + out_vec = v2_norm(out_vec); + out_vec = v2_mul(out_vec, 5); + + { + /* Spawn bullet */ + struct entity *bullet = entity_alloc(root); + bullet->sprite = sprite_tag_from_path(STR("res/graphics/bullet.ase")); + struct xform bullet_xf = XFORM_POS(out_pos); + entity_set_xform(bullet, bullet_xf); + bullet->velocity = out_vec; + } + } + } + /* ========================== * * Update camera position * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; @@ -589,7 +619,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) * Update sound emitters * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; @@ -622,7 +652,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) struct entity **ents_to_release = arena_dry_push(temp.arena, struct entity *); u64 ents_to_release_count = 0; - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!ent->valid) continue; @@ -689,36 +719,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) publish_game_tick(); __profframe("Game"); - /* ========================== * - * Reset triggered entities - * ========================== */ - - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { - struct entity *ent = &store->entities[entity_index]; - if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; - - if (entity_has_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK)) { - entity_disable_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK); - entity_enable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK); - } else if (entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) { - entity_disable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK); - } - } - - /* ========================== * - * Activate entities - * ========================== */ - - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { - struct entity *ent = &store->entities[entity_index]; - if (!ent->valid) continue; - - if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) { - entity_enable_prop(ent, ENTITY_PROP_ACTIVE); - ++ent->continuity_gen; - } - } - /* ========================== * * End frame cache scopes * ========================== */ @@ -749,10 +749,23 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg) if (!G.paused) { game_update(game_cmds); } - /* Check for pause cmd */ + /* Check for pause / next frame cmds */ for (u64 i = 0; i < game_cmds.count; ++i) { - if (game_cmds.cmds[i].kind == GAME_CMD_KIND_PAUSE) { - G.paused = !G.paused; + struct game_cmd cmd = game_cmds.cmds[i]; + switch (cmd.kind) { + case GAME_CMD_KIND_PAUSE: { + G.paused = !G.paused; + } break; + + case GAME_CMD_KIND_STEP: { + if (G.paused) { + G.paused = false; + game_update(game_cmds); + G.paused = true; + } + } break; + + default: break; } } } diff --git a/src/game.h b/src/game.h index 44e2d70d..9eb3a7e3 100644 --- a/src/game.h +++ b/src/game.h @@ -22,6 +22,7 @@ enum game_cmd_kind { GAME_CMD_KIND_CLEAR_ALL, GAME_CMD_KIND_SPAWN_TEST, GAME_CMD_KIND_PAUSE, + GAME_CMD_KIND_STEP, GAME_CMD_KIND_COUNT }; diff --git a/src/user.c b/src/user.c index dcf6c8ce..6d105432 100644 --- a/src/user.c +++ b/src/user.c @@ -86,6 +86,7 @@ GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = { [SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR, [SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN, + [SYS_BTN_N] = USER_BIND_KIND_DEBUG_STEP, [SYS_BTN_F1] = USER_BIND_KIND_DEBUG_PAUSE, [SYS_BTN_F2] = USER_BIND_KIND_DEBUG_CAMERA, [SYS_BTN_F3] = USER_BIND_KIND_DEBUG_DRAW, @@ -440,24 +441,29 @@ INTERNAL void user_update(void) tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f); } - /* TODO: Should we actually be basing interpolated tick on t1 rather - * than t0? This means un-interpolated values will use the newer frame's - * value, while still interpolating from the older frame. */ - world_copy_replace(&G.world, t1); + world_copy_replace(&G.world, t0); /* Blend world globals */ G.world.time = math_lerp64(t0->time, t1->time, (f64)tick_blend); /* Blend entities */ - u64 num_entities = min_u64(t0->entity_store->count, t1->entity_store->count); + u64 num_entities = min_u64(t0->entity_store->reserved, t1->entity_store->reserved); { __profscope(tick_blending); for (u64 i = 0; i < num_entities; ++i) { + struct entity *e = &store->entities[i]; struct entity *e0 = &t0->entity_store->entities[i]; struct entity *e1 = &t1->entity_store->entities[i]; - struct entity *e = &store->entities[i]; - ASSERT(!e->valid || e->cached_global_xform_dirty == false); /* Game thread should have cached all global xforms before publishing */ - if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) { + ASSERT(e->cached_global_xform_dirty == false); /* Game thread should have cached all global xforms before publishing */ + + if (entity_has_prop(e, ENTITY_PROP_TEST)) { + DEBUGBREAKABLE; + } + + if (e0->valid && e1->valid + && entity_has_prop(e0, ENTITY_PROP_ACTIVE) && entity_has_prop(e1, ENTITY_PROP_ACTIVE) + && e0->handle.gen == e1->handle.gen + && e0->continuity_gen == e1->continuity_gen) { e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, tick_blend); e->cached_global_xform = xform_lerp(e0->cached_global_xform, e1->cached_global_xform, tick_blend); @@ -586,6 +592,16 @@ INTERNAL void user_update(void) } } + /* Test step */ + { + struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_STEP]; + if (state.num_presses) { + queue_game_cmd(&cmd_list, (struct game_cmd) { + .kind = GAME_CMD_KIND_STEP + }); + } + } + /* Test spawn */ { struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN]; @@ -770,16 +786,16 @@ INTERNAL void user_update(void) * Draw entities * ========================== */ - for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { __profscope(user_entity_iter); struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; - if (ent->is_root) continue; struct sprite_tag sprite = ent->sprite; /* Skip undrawable entities */ - if (sprite_tag_is_nil(sprite) && !entity_has_prop(ent, ENTITY_PROP_CAMERA)) { + if (ent->is_root + || (sprite_tag_is_nil(sprite) && !entity_has_prop(ent, ENTITY_PROP_CAMERA))) { continue; } @@ -1030,6 +1046,9 @@ INTERNAL void user_update(void) draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world time: %F"), FMT_FLOAT((f64)G.world.time))); pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("entities: %F/%F"), FMT_UINT(G.world.entity_store->allocated), FMT_UINT(G.world.entity_store->reserved))); + pos.y += spacing; + draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y))); pos.y += spacing; diff --git a/src/user.h b/src/user.h index ffae5355..4ce17630 100644 --- a/src/user.h +++ b/src/user.h @@ -27,6 +27,7 @@ enum user_bind_kind { USER_BIND_KIND_DEBUG_DRAW, USER_BIND_KIND_DEBUG_CAMERA, USER_BIND_KIND_DEBUG_PAUSE, + USER_BIND_KIND_DEBUG_STEP, USER_BIND_KIND_FULLSCREEN, USER_BIND_KIND_ZOOM_IN, USER_BIND_KIND_ZOOM_OUT,