From 38410073cf62e6553e2d7130d3caff8f44759c9b Mon Sep 17 00:00:00 2001 From: doyle Date: Sat, 23 Sep 2023 16:42:22 +1000 Subject: [PATCH] fp: Add mob spawner and profiling metrics --- External/tely | 2 +- feely_pona.cpp | 204 +++++++++++++++++++++++++------------------- feely_pona.h | 14 +++ feely_pona_game.cpp | 21 ++++- feely_pona_game.h | 19 +++-- feely_pona_unity.h | 1 + 6 files changed, 165 insertions(+), 96 deletions(-) create mode 100644 feely_pona.h diff --git a/External/tely b/External/tely index 2e846b0..5a56d52 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit 2e846b010ab9c32e3792375aa5db23796201b3ee +Subproject commit 5a56d52e71c4b66a9c6fa795a5a851f0dd4206cb diff --git a/feely_pona.cpp b/feely_pona.cpp index 9411f87..1276ee7 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -478,8 +478,8 @@ void TELY_DLL_Init(void *user_data) // NOTE: Hero { - FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Hero"); - entity->local_pos = Dqn_V2_InitNx2(100.f, 100.f); + FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry"); + entity->local_pos = Dqn_V2_InitNx2(1334, 396); entity->size_scale = Dqn_V2_InitNx1(4); entity->sprite_sheet = &game->hero_sprite_sheet; entity->sprite_anims = game->hero_sprite_anims; @@ -488,27 +488,15 @@ void TELY_DLL_Init(void *user_data) entity->flags |= FP_GameEntityFlag_MoveByKeyboard; entity->flags |= FP_GameEntityFlag_MoveByMouse; entity->flags |= FP_GameEntityFlag_NonTraversable; + entity->facing_left = true; game->clicked_entity = entity->handle; - } - - // NOTE: Enemy - { - FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Enemy"); - entity->local_pos = Dqn_V2_InitNx2(300.f, 300.f); - entity->size_scale = Dqn_V2_InitNx1(4); - entity->sprite_sheet = &game->hero_sprite_sheet; - entity->sprite_anims = game->hero_sprite_anims; - entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); - entity->flags |= FP_GameEntityFlag_Clickable; - entity->flags |= FP_GameEntityFlag_MoveByKeyboard; - entity->flags |= FP_GameEntityFlag_MoveByMouse; - entity->flags |= FP_GameEntityFlag_NonTraversable; + game->player = entity->handle; } game->tile_size = 37; Dqn_V2I max_tile = platform->core.window_size / game->tile_size; - // NOTE: Wall + // NOTE: Wall ================================================================================== { Dqn_V2I vert_wall_tile_size = Dqn_V2I_InitNx2(1, 12); Dqn_V2I right_wall_tile_pos = Dqn_V2I_InitNx2(max_tile.x - vert_wall_tile_size.x - 0, (max_tile.y / 2.f) - (vert_wall_tile_size.y * .5f)); @@ -551,6 +539,16 @@ void TELY_DLL_Init(void *user_data) } } + // NOTE: Mob spawner + { + FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob spawner"); + entity->local_pos = Dqn_V2_InitNx2(0, platform->core.window_size.y * .5f); + entity->flags |= FP_GameEntityFlag_Clickable; + entity->flags |= FP_GameEntityFlag_MoveByKeyboard; + entity->flags |= FP_GameEntityFlag_MoveByMouse; + entity->flags |= FP_GameEntityFlag_MobSpawner; + } + uint16_t font_size = 18; game->camera.scale = Dqn_V2_InitNx1(1); game->inter_regular_font = platform->func_load_font(assets, DQN_STRING8("Inter (Regular)"), DQN_STRING8("Data/Inter-Regular.otf"), font_size); @@ -561,6 +559,8 @@ void TELY_DLL_Init(void *user_data) void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, TELY_PlatformInput *input) { + Dqn_Profiler_ZoneScopeWithIndex("FP_Update", FP_ProfileZone_FPUpdate); + if (TELY_Platform_InputKeyIsReleased(input->mouse_left)) game->clicked_entity = game->prev_active_entity; @@ -586,6 +586,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, game->camera.world_pos += dir_vector * 5.f; } + Dqn_ProfilerZone update_zone = Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8("FP_Update: Entity loop"), FP_ProfileZone_FPUpdate_EntityLoop); for (FP_GameEntityIterator it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { FP_GameEntity *entity = it.entity; entity->alive_time_s += input->delta_s; @@ -602,15 +603,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, } // NOTE: Stalk entity ====================================================================== -#if 1 Dqn_V2 entity_world_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); - if (entity->name == DQN_STRING8("Enemy")) { - if (entity->handle != game->clicked_entity && entity->stalk_entity != game->clicked_entity) { - entity->stalk_entity = game->clicked_entity; - } - + { + Dqn_Profiler_ZoneScopeWithIndex("FP_Update: Path finding", FP_ProfileZone_FPUpdate_PathFinding); FP_GameEntity *stalk_entity = FP_Game_GetEntity(game, entity->stalk_entity); - if (stalk_entity) { + if (stalk_entity->handle.id) { Dqn_V2 stalk_world_pos = FP_Game_CalcEntityWorldPos(game, stalk_entity->handle); Dqn_V2I stalk_tile = Dqn_V2I_InitNx2(stalk_world_pos.x / game->tile_size, stalk_world_pos.y / game->tile_size); if (entity->stalk_entity_last_known_tile != stalk_tile) { @@ -636,30 +633,29 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, } } } - } - // NOTE: Render the waypoints - for (FP_GameWaypoint *waypoint = entity->waypoints->next; waypoint != entity->waypoints; waypoint = waypoint->next) { - Dqn_V2 circle_pos = Dqn_V2_InitNx2(waypoint->pos.x * game->tile_size + game->tile_size * .5f, waypoint->pos.y * game->tile_size + game->tile_size * .5f); - TELY_Render_CircleColourV4(renderer, circle_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_MAGENTA_V4); - } + // NOTE: Render the waypoints + for (FP_GameWaypoint *waypoint = entity->waypoints->next; waypoint != entity->waypoints; waypoint = waypoint->next) { + Dqn_V2 circle_pos = Dqn_V2_InitNx2(waypoint->pos.x * game->tile_size + game->tile_size * .5f, waypoint->pos.y * game->tile_size + game->tile_size * .5f); + TELY_Render_CircleColourV4(renderer, circle_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_MAGENTA_V4); + } - if (entity->waypoints->next != entity->waypoints) { - FP_GameWaypoint *waypoint = entity->waypoints->next; - Dqn_V2I target_tile = entity->waypoints->next->pos; - Dqn_V2 target_pos = Dqn_V2_InitNx2(target_tile.x * game->tile_size + game->tile_size *.5f, target_tile.y * game->tile_size + game->tile_size * .5f); - Dqn_V2 entity_to_target_pos = target_pos - entity_world_pos; + if (entity->waypoints->next != entity->waypoints) { + FP_GameWaypoint *waypoint = entity->waypoints->next; + Dqn_V2I target_tile = entity->waypoints->next->pos; + Dqn_V2 target_pos = Dqn_V2_InitNx2(target_tile.x * game->tile_size + game->tile_size *.5f, target_tile.y * game->tile_size + game->tile_size * .5f); + Dqn_V2 entity_to_target_pos = target_pos - entity_world_pos; - if (Dqn_V2_LengthSq(entity_to_target_pos) < DQN_SQUARED(entity->local_hit_box_size.x * .5f)) { - waypoint->next->prev = waypoint->prev; - waypoint->prev->next = waypoint->next; - TELY_ChunkPool_Dealloc(game->chunk_pool, waypoint); - } else { - Dqn_V2 entity_to_target_pos_norm = Dqn_V2_Normalise(entity_to_target_pos); - entity->local_pos += entity_to_target_pos_norm * (entity->local_hit_box_size.x * .05f); + if (Dqn_V2_LengthSq(entity_to_target_pos) < DQN_SQUARED(entity->local_hit_box_size.x * .5f)) { + waypoint->next->prev = waypoint->prev; + waypoint->prev->next = waypoint->next; + TELY_ChunkPool_Dealloc(game->chunk_pool, waypoint); + } else { + Dqn_V2 entity_to_target_pos_norm = Dqn_V2_Normalise(entity_to_target_pos); + entity->local_pos += entity_to_target_pos_norm * (entity->local_hit_box_size.x * .05f); + } } } -#endif // NOTE: Core equations of motion ========================================================== { @@ -895,9 +891,17 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, } else { entity->attack_box_size = {}; } + + // NOTE: Mob spawner ======================================================================= + if ((entity->flags & FP_GameEntityFlag_MobSpawner) && input->timer_s >= entity->next_spawn_timestamp_s) { + entity->next_spawn_timestamp_s = DQN_CAST(uint64_t)(input->timer_s + 5.f); + FP_Game_EntityAddMob(game, entity_world_pos); + } } + Dqn_Profiler_EndZone(update_zone); // NOTE: Do attacks ============================================================================ + auto attack_zone = Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8("FP_Update: Attacks"), FP_ProfileZone_FPUpdate_Attacks); for (FP_GameEntityIterator attacker_it = {}; FP_Game_DFSPostOrderWalkEntityTree(game, &attacker_it, game->root_entity); ) { FP_GameEntity *attacker = attacker_it.entity; @@ -932,10 +936,12 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, defender->velocity = (acceleration * t) + defender->velocity * 2.0f; } } + Dqn_Profiler_EndZone(attack_zone); } void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) { + Dqn_Profiler_ZoneScopeWithIndex("FP_Render", FP_ProfileZone_FPRender); TELY_PlatformInput *input = &platform->input; Dqn_M2x3 model_view = FP_Game_CameraModelViewM2x3(game->camera, platform); Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2(model_view, input->mouse_p); @@ -1117,63 +1123,85 @@ void TELY_DLL_FrameUpdate(void *user_data) FP_Update(platform, game, renderer, input); FP_Render(game, platform, renderer); + + // NOTE: UI ==================================================================================== { TELY_Render_PushTransform(renderer, Dqn_M2x3_Identity()); DQN_DEFER { TELY_Render_PopTransform(renderer); }; - TELY_RFuiResult info_bar = TELY_RFui_Row(rfui, DQN_STRING8("Info Bar")); - info_bar.widget->semantic_position[TELY_RFuiAxis_X].kind = TELY_RFuiPositionKind_Absolute; - info_bar.widget->semantic_position[TELY_RFuiAxis_X].value = 10.f; - info_bar.widget->semantic_position[TELY_RFuiAxis_Y].kind = TELY_RFuiPositionKind_Absolute; - info_bar.widget->semantic_position[TELY_RFuiAxis_Y].value = 10.f; + // NOTE: Info bar ========================================================================== + { + TELY_RFuiResult info_bar = TELY_RFui_Row(rfui, DQN_STRING8("Info Bar")); + info_bar.widget->semantic_position[TELY_RFuiAxis_X].kind = TELY_RFuiPositionKind_Absolute; + info_bar.widget->semantic_position[TELY_RFuiAxis_X].value = 10.f; + info_bar.widget->semantic_position[TELY_RFuiAxis_Y].kind = TELY_RFuiPositionKind_Absolute; + info_bar.widget->semantic_position[TELY_RFuiAxis_Y].value = 10.f; - TELY_RFui_PushParent(rfui, info_bar.widget); - DQN_DEFER { TELY_RFui_PopParent(rfui); }; + TELY_RFui_PushParent(rfui, info_bar.widget); + DQN_DEFER { TELY_RFui_PopParent(rfui); }; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_String8Builder builder = {}; - builder.allocator = scratch.allocator; + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + TELY_RFui_TextF(rfui, "TELY"); + if (Dqn_String8_IsValid(platform->core.os_name)) { + TELY_RFui_TextF(rfui, " | %.*s", DQN_STRING_FMT(platform->core.os_name)); + } - TELY_RFui_TextF(rfui, "TELY"); - Dqn_String8Builder_AppendF(&builder, "TELY"); - if (Dqn_String8_IsValid(platform->core.os_name)) { - TELY_RFui_TextF(rfui, " | %.*s", DQN_STRING_FMT(platform->core.os_name)); - Dqn_String8Builder_AppendF(&builder, " | %.*s", DQN_STRING_FMT(platform->core.os_name)); + TELY_RFui_TextF(rfui, + " | %dx%d %.1fHz | TSC %.1f GHz", + platform->core.display.size.w, + platform->core.display.size.h, + platform->core.display.refresh_rate, + platform->core.tsc_per_second / 1'000'000'000.0); + + if (platform->core.ram_mb) + TELY_RFui_TextF(rfui, " | RAM %.1fGB", platform->core.ram_mb / 1024.0); + + TELY_RFui_TextF(rfui, + " | Work %04.1fms/f (%04.1f%%) | %05.1f FPS | Frame %'I64u | Timer %.1fs", + input->work_ms, + input->work_ms * 100.0 / input->delta_ms, + 1000.0 / input->delta_ms, + input->frame_counter, + input->timer_s); } - Dqn_String8Builder_AppendF(&builder, - " | %dx%d %.1fHz | TSC %.1f GHz", - platform->core.display.size.w, - platform->core.display.size.h, - platform->core.display.refresh_rate, - platform->core.tsc_per_second / 1'000'000'000.0); + // NOTE: Profiler + { + TELY_RFuiResult profiler_layout = TELY_RFui_Column(rfui, DQN_STRING8("Profiler Bar")); + profiler_layout.widget->semantic_position[TELY_RFuiAxis_X].kind = TELY_RFuiPositionKind_Absolute; + profiler_layout.widget->semantic_position[TELY_RFuiAxis_X].value = 10.f; + profiler_layout.widget->semantic_position[TELY_RFuiAxis_Y].kind = TELY_RFuiPositionKind_Absolute; + profiler_layout.widget->semantic_position[TELY_RFuiAxis_Y].value = TELY_Asset_GetFont(assets, TELY_RFui_ActiveFont(rfui))->pixel_height * 1.5f; - TELY_RFui_TextF(rfui, - " | %dx%d %.1fHz | TSC %.1f GHz", - platform->core.display.size.w, - platform->core.display.size.h, - platform->core.display.refresh_rate, - platform->core.tsc_per_second / 1'000'000'000.0); + TELY_RFui_PushParent(rfui, profiler_layout.widget); + DQN_DEFER { TELY_RFui_PopParent(rfui); }; - if (platform->core.ram_mb) { - Dqn_String8Builder_AppendF(&builder, " | RAM %.1fGB", platform->core.ram_mb / 1024.0); - TELY_RFui_TextF(rfui, " | RAM %.1fGB", platform->core.ram_mb / 1024.0); + Dqn_ProfilerAnchor *anchors = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back); + for (size_t anchor_index = 1; anchor_index < DQN_PROFILER_ANCHOR_BUFFER_SIZE; anchor_index++) { + Dqn_ProfilerAnchor const *anchor = anchors + anchor_index; + if (!anchor->hit_count) + continue; + + uint64_t tsc_exclusive = anchor->tsc_exclusive; + uint64_t tsc_inclusive = anchor->tsc_inclusive; + Dqn_f64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DQN_CAST(Dqn_f64)platform->core.tsc_per_second; + if (tsc_exclusive == tsc_inclusive) { + TELY_RFui_TextF(rfui, + "%.*s[%u]: %.1fms", + DQN_STRING_FMT(anchor->name), + anchor->hit_count, + tsc_exclusive_milliseconds); + } else { + Dqn_f64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DQN_CAST(Dqn_f64)platform->core.tsc_per_second; + TELY_RFui_TextF(rfui, + "%.*s[%u]: %.1f/%.1fms", + DQN_STRING_FMT(anchor->name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds); + } + } } - - Dqn_String8Builder_AppendF(&builder, - " | Work %04.1fms/f (%04.1f%%) | %05.1f FPS | Frame %'I64u | Timer %.1fs", - input->work_ms, - input->work_ms * 100.0 / input->delta_ms, - 1000.0 / input->delta_ms, - input->frame_counter, - input->timer_s); - TELY_RFui_TextF(rfui, - " | Work %04.1fms/f (%04.1f%%) | %05.1f FPS | Frame %'I64u | Timer %.1fs", - input->work_ms, - input->work_ms * 100.0 / input->delta_ms, - 1000.0 / input->delta_ms, - input->frame_counter, - input->timer_s); } TELY_RFui_Flush(rfui, renderer, input, assets); diff --git a/feely_pona.h b/feely_pona.h new file mode 100644 index 0000000..df4b921 --- /dev/null +++ b/feely_pona.h @@ -0,0 +1,14 @@ +#if defined(__clang__) +#pragma once +#include "feely_pona_unity.h" +#endif + +enum FP_ProfileZone +{ + FP_ProfileZone_FPUpdate = TELY_ProfileZone_Count, + FP_ProfileZone_FPUpdate_EntityLoop, + FP_ProfileZone_FPUpdate_PathFinding, + FP_ProfileZone_FPUpdate_Attacks, + FP_ProfileZone_FPRender, +}; + diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 8cbe0c7..354d252 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -472,8 +472,6 @@ static Dqn_Slice FP_Game_AStarPathFind(FP_Game *game, DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); }; // NOTE: Enumerate the entities that are collidable ============================================ - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_List colliders = Dqn_List_Init(scratch.arena, 128); for (FP_GameEntityIterator it = {}; FP_Game_DFSPreOrderWalkEntityTree(game, &it, game->root_entity); ) { FP_GameEntity const *walk_entity = it.entity; if (entity == walk_entity->handle) @@ -606,6 +604,7 @@ static Dqn_Slice FP_Game_AStarPathFind(FP_Game *game, return result; } +// NOTE: Spawning code ============================================================================= static FP_GameEntityHandle FP_Game_EntityAddWallAtTile(FP_Game *game, Dqn_String8 name, Dqn_V2I tile_pos, Dqn_V2I size_in_tiles) { Dqn_V2 size = Dqn_V2_InitV2I(size_in_tiles * DQN_CAST(int32_t) game->tile_size); @@ -628,3 +627,21 @@ static FP_GameEntityHandle FP_Game_EntityAddWallAtTile(FP_Game *game, Dqn_String FP_GameEntityHandle result = entity->handle; return result; } + +static FP_GameEntityHandle FP_Game_EntityAddMob(FP_Game *game, Dqn_V2 pos) +{ + FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob"); + entity->local_pos = pos; + entity->size_scale = Dqn_V2_InitNx1(4); + entity->sprite_sheet = &game->hero_sprite_sheet; + entity->sprite_anims = game->hero_sprite_anims; + entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); + entity->flags |= FP_GameEntityFlag_Clickable; + entity->flags |= FP_GameEntityFlag_MoveByKeyboard; + entity->flags |= FP_GameEntityFlag_MoveByMouse; + entity->flags |= FP_GameEntityFlag_NonTraversable; + entity->stalk_entity = game->player; + + FP_GameEntityHandle result = entity->handle; + return result; +} diff --git a/feely_pona_game.h b/feely_pona_game.h index 8292e4f..34f4649 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -11,6 +11,7 @@ enum FP_GameEntityFlag FP_GameEntityFlag_DrawHitBox = 1 << 3, FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 4, FP_GameEntityFlag_NonTraversable = 1 << 5, + FP_GameEntityFlag_MobSpawner = 1 << 6, }; enum FP_GameShapeType @@ -79,6 +80,12 @@ struct FP_GameEntityAction struct FP_GameEntity { + FP_GameEntity *next; + FP_GameEntity *prev; + FP_GameEntity *first_child; + FP_GameEntity *last_child; + FP_GameEntity *parent; + Dqn_String8 name; FP_GameEntityHandle handle; TELY_AssetSpriteSheet *sprite_sheet; @@ -98,16 +105,13 @@ struct FP_GameEntity Dqn_V2 attack_box_size; Dqn_V2 attack_box_offset; + uint64_t next_spawn_timestamp_s; + uint64_t flags; bool facing_left; Dqn_V2 local_pos; Dqn_f64 alive_time_s; Dqn_FArray shapes; - FP_GameEntity *next; - FP_GameEntity *prev; - FP_GameEntity *first_child; - FP_GameEntity *last_child; - FP_GameEntity *parent; }; struct FP_GameEntityIterator @@ -142,14 +146,19 @@ struct FP_Game TELY_AssetSpriteSheet hero_sprite_sheet; Dqn_FArray parent_entity_stack; Dqn_VArray entities; + FP_GameEntity *root_entity; FP_GameEntity *entity_free_list; + + FP_GameEntityHandle player; + FP_GameEntityHandle clicked_entity; FP_GameEntityHandle hot_entity; FP_GameEntityHandle active_entity; FP_GameEntityHandle prev_clicked_entity; FP_GameEntityHandle prev_hot_entity; FP_GameEntityHandle prev_active_entity; + FP_GameCamera camera; TELY_RFui rfui; }; diff --git a/feely_pona_unity.h b/feely_pona_unity.h index 5897c0f..91f40c2 100644 --- a/feely_pona_unity.h +++ b/feely_pona_unity.h @@ -62,6 +62,7 @@ DQN_MSVC_WARNING_DISABLE(4505) // warning C4505: unreferenced function with inte // NOTE: feely_pona ================================================================================ #include "feely_pona_game.h" +#include "feely_pona.h" #include "feely_pona_game.cpp" #include "feely_pona.cpp"