diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2024-03-24 15:32:25 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2024-03-24 15:35:58 +0100 |
commit | fe0d29dccc73228091fd4e2e470c5391d3045220 (patch) | |
tree | de42adcafeb52ead55cc2a78a5dfd9304c2c8cf1 | |
parent | 38f23709a787861737f5d86d174df4abcddd6115 (diff) |
bench: measure critter::update_movement() perf
-rw-r--r-- | bench/critter.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/bench/critter.cpp b/bench/critter.cpp new file mode 100644 index 00000000..354c4f8b --- /dev/null +++ b/bench/critter.cpp @@ -0,0 +1,354 @@ +#include "compat/debug.hpp" +#include "compat/shared-ptr-wrapper.hpp" +#include "compat/function2.hpp" +#include "src/critter.hpp" +#include "src/world.hpp" +#include "src/wall-atlas.hpp" +#include "src/nanosecond.inl" +#include "src/log.hpp" +#include "src/point.inl" +#include "loader/loader.hpp" +#include <cinttypes> +#include <cstdio> +#include <benchmark/benchmark.h> + +namespace floormat { + +namespace { + +using enum rotation; +using fu2::function_view; +using Function = function_view<Ns() const>; + +#ifndef __CLION_IDE__ +constexpr auto constantly(const auto& x) noexcept { + return [x]<typename... Ts> (const Ts&...) constexpr -> const auto& { return x; }; +} +#else +constexpr auto constantly(Ns x) noexcept { return [x] { return x; }; } +#endif + +critter_proto make_proto(float accel) +{ + critter_proto proto; + proto.atlas = loader.anim_atlas("npc-walk", loader.ANIM_PATH); + proto.name = "Player"_s; + proto.speed = accel; + proto.playable = true; + proto.offset = {}; + proto.bbox_offset = {}; + proto.bbox_size = Vector2ub(tile_size_xy/2); + return proto; +} + +void mark_all_modified(world& w) +{ + for (auto& [coord, ch] : w.chunks()) + ch.mark_modified(); +} + +struct Start +{ + StringView name, instance; + point pt; + double accel = 1; + enum rotation rotation = N; +#if 1 + static constexpr bool quiet = true, verbose = false; +#elif 1 + bool quiet = !is_log_verbose(); + bool verbose = false; +#elif 0 + bool verbose = true; + bool quiet = false; +#elif 1 + bool verbose = false; + bool quiet = false; +#elif 0 + bool quiet = is_log_quiet(); + bool verbose = is_log_standard() || is_log_verbose(); +#else + bool quiet = is_log_quiet() || is_log_standard(); + bool verbose = is_log_verbose(); +#endif +}; + +struct Expected +{ + point pt; + Ns time; +}; + +struct Grace +{ + Ns time = Ns{250}; + uint32_t distance_L2 = 24; + bool no_crash = false; +}; + +bool run(world& w, const function_view<Ns() const>& make_dt, + Start start, Expected expected, Grace grace = {}) +{ + constexpr auto max_time = 300*Second; + constexpr uint32_t max_steps = 100'000; + + fm_assert(grace.time != Ns{}); + fm_assert(!start.quiet | !start.verbose); + //validate_start(start); + //validate_expected(expected); + //validate_grace(grace); + fm_assert(start.accel > 1e-8); + fm_assert(start.accel <= 50); + fm_assert(start.name); + fm_assert(start.instance); + fm_assert(start.rotation < rotation_COUNT); + expected.time.stamp = uint64_t(expected.time.stamp / start.accel); + fm_assert(expected.time <= max_time); + fm_assert(grace.distance_L2 <= (uint32_t)Vector2((iTILE_SIZE2 * TILE_MAX_DIM)).length()); + + mark_all_modified(w); + + object_id id = 0; + auto npc_ = w.ensure_player_character(id, make_proto((float)start.accel)).ptr; + auto& npc = *npc_; + + auto index = npc.index(); + npc.teleport_to(index, start.pt, rotation_COUNT); + + Ns time{0}, saved_time{0}; + auto last_pos = npc.position(); + uint32_t i; + constexpr auto max_stop_frames = 250; // todo! detect collisions properly and don't rely on this + uint32_t frames_stopped = 0; + + if (!start.quiet) [[unlikely]] + Debug{} << "**" << start.name << start.instance << colon(); + + constexpr auto print_pos = [](StringView prefix, point start, point pos, Ns time, Ns dt, const critter& npc) { + DBG_nospace << prefix + << " " << pos + << " time:" << time + << " dt:" << dt + << " dist:" << point::distance_l2(pos, start) + << " delta:" << npc.delta + << " frac:" << npc.offset_frac_; + }; + + auto fail = [b = grace.no_crash](const char* file, int line) { + if (b) [[likely]] + return false; + else + { + fm_assert(false); + fm_EMIT_DEBUG("", "assertion failed: false in %s:%d", file, line); + fm_EMIT_ABORT(); + } + }; + + for (i = 0; true; i++) + { + const auto dt = Ns{make_dt()}; + if (dt == Ns{}) [[unlikely]] + { + if (start.verbose) [[unlikely]] + Debug{} << "| dt == 0, breaking"; + break; + } + if (start.verbose) [[unlikely]] + print_pos(" ", expected.pt, npc.position(), time, dt, npc); + fm_assert(dt >= Millisecond*1e-1); + fm_assert(dt <= Second * 1000); + npc.update_movement(index, dt, start.rotation); + const auto pos = npc.position(); + const bool same_pos = pos == last_pos; + last_pos = pos; + + time += dt; + + if (same_pos) + { + frames_stopped++; + if (frames_stopped >= max_stop_frames) [[unlikely]] + { + if (!start.quiet) [[unlikely]] + { + print_pos("->", expected.pt, pos, time, dt, npc); + DBG_nospace << "===>" + << " iters," + << " time:" << time + << " distance:" << point::distance_l2(last_pos, expected.pt) << " px" + << Debug::newline; + } + break; + } + } + else + { + frames_stopped = 0; + saved_time = time; + } + + if (time > max_time) [[unlikely]] + { + if (!start.quiet) [[unlikely]] + print_pos("*", start.pt, last_pos, time, dt, npc); + Error{standard_error()} << "!!! fatal: timeout" << max_time << "reached!"; + return fail(__FILE__, __LINE__); + } + if (i > max_steps) [[unlikely]] + { + if (!start.quiet) [[unlikely]] + print_pos("*", start.pt, last_pos, time, dt, npc); + Error{standard_error()} << "!!! fatal: position doesn't converge after" << i << "iterations!"; + return fail(__FILE__, __LINE__); + } + } + + if (const auto dist_l2 = point::distance_l2(last_pos, expected.pt); + dist_l2 > grace.distance_L2) [[unlikely]] + { + Error{standard_error()} << "!!! fatal: distance" << dist_l2 << "pixels" << "over grace distance of" << grace.distance_L2; + return fail(__FILE__, __LINE__); + } + else if (start.verbose) [[unlikely]] + Debug{} << "*" << "distance:" << dist_l2 << "pixels"; + + if (expected.time != Ns{}) [[likely]] + { + const auto time_diff = Ns{Math::abs((int64_t)expected.time.stamp - (int64_t)saved_time.stamp)}; + if (time_diff > grace.time) + { + Error{ standard_error(), Debug::Flag::NoSpace } + << "!!! fatal: wrong time " << saved_time + << " expected:" << expected.time + << " diff:" << time_diff + << " for " << start.name << "/" << start.instance; + return fail(__FILE__, __LINE__); + } + } + + return true; +} + +void test1(StringView instance_name, const Function& make_dt, double accel) +{ + const auto W = wall_image_proto{ loader.wall_atlas("empty"), 0 }; + + auto w = world(); + w[{{0,0,0}, {8,9}}].t.wall_north() = W; + w[{{0,1,0}, {8,0}}].t.wall_north() = W; + + bool ret = run(w, make_dt, + Start{ + .name = "test1"_s, + .instance = instance_name, + .pt = {{0,0,0}, {8,15}, {-8, 8}}, + .accel = accel, + .rotation = N, + }, + Expected{ + .pt = {{0,0,0}, {8, 9}, {-6,-15}}, // distance_L2 == 3 + .time = 6950*Millisecond, + }, + Grace{ + .time = 300*Millisecond, + }); + fm_assert(ret); +} + +void test2(StringView instance_name, const Function& make_dt, double accel) +{ + const auto W = wall_image_proto{ loader.wall_atlas("empty"), 0 }; + + auto w = world(); + w[{{-1,-1,0}, {13,13}}].t.wall_north() = W; + w[{{-1,-1,0}, {13,13}}].t.wall_west() = W; + w[{{1,1,0}, {4,5}}].t.wall_north() = W; + w[{{1,1,0}, {5,4}}].t.wall_west() = W; + + bool ret = run(w, make_dt, + Start{ + .name = "test2"_s, + .instance = instance_name, + .pt = {{-1,-1,0}, {13,14}, {-15,-29}}, + .accel = accel, + .rotation = SE, + }, + Expected{ + .pt = {{1,1,0}, {4, 4}, {8,8}}, + .time = 35'100*Millisecond, + }, + Grace{ + .time = 500*Millisecond, + .distance_L2 = 8, + }); + fm_assert(ret); +} + +void test_critter() +{ + // todo! add ANSI sequence to stdout to goto start of line and clear to eol + // \r + // <ESC>[2K + // \n + + constexpr bool is_noisy = false; + if (is_noisy) + DBG_nospace << ""; + + test1("dt=16.667 accel=1", constantly(Millisecond * 16.667), 1); + test1("dt=16.667 accel=2", constantly(Millisecond * 16.667), 2); + test1("dt=16.667 accel=5", constantly(Millisecond * 16.667), 5); + test1("dt=16.667 accel=0.5", constantly(Millisecond * 16.667), 0.5); + test1("dt=33.334 accel=1", constantly(Millisecond * 33.334), 1); + test1("dt=33.334 accel=2", constantly(Millisecond * 33.334), 2); + test1("dt=33.334 accel=5", constantly(Millisecond * 33.334), 5); + test1("dt=33.334 accel=10", constantly(Millisecond * 33.334), 10); + test1("dt=50.000 accel=1", constantly(Millisecond * 50.000), 1); + test1("dt=50.000 accel=2", constantly(Millisecond * 50.000), 2); + test1("dt=50.000 accel=5", constantly(Millisecond * 50.000), 5); + test1("dt=100.00 accel=1", constantly(Millisecond * 100.00), 1); + test1("dt=100.00 accel=2", constantly(Millisecond * 100.00), 2); + test1("dt=100.00 accel=0.5", constantly(Millisecond * 100.00), 0.5); + test1("dt=200.00 accel=1", constantly(Millisecond * 200.00), 1); + test1("dt=1.0000 accel=1", constantly(Millisecond * 1.0000), 1); + test1("dt=1.0000 accel=0.5", constantly(Millisecond * 1.0000), 0.5); + + test2("dt=16.667 accel=1", constantly(Millisecond * 16.667), 1); + test2("dt=16.667 accel=2", constantly(Millisecond * 16.667), 2); + test2("dt=16.667 accel=5", constantly(Millisecond * 16.667), 5); + test2("dt=16.667 accel=0.5", constantly(Millisecond * 16.667), 0.5); + test2("dt=33.334 accel=1", constantly(Millisecond * 33.334), 1); + test2("dt=33.334 accel=2", constantly(Millisecond * 33.334), 2); + test2("dt=33.334 accel=5", constantly(Millisecond * 33.334), 5); + test2("dt=33.334 accel=10", constantly(Millisecond * 33.334), 10); + test2("dt=50.000 accel=1", constantly(Millisecond * 50.000), 1); + test2("dt=50.000 accel=2", constantly(Millisecond * 50.000), 2); + test2("dt=50.000 accel=5", constantly(Millisecond * 50.000), 5); + test2("dt=100.00 accel=1", constantly(Millisecond * 100.00), 1); + test2("dt=100.00 accel=2", constantly(Millisecond * 100.00), 2); + test2("dt=100.00 accel=0.5", constantly(Millisecond * 100.00), 0.5); + test2("dt=200.00 accel=1", constantly(Millisecond * 200.00), 1); + test2("dt=1.0000 accel=1", constantly(Millisecond * 1.0000), 1); + test2("dt=1.0000 accel=0.5", constantly(Millisecond * 1.0000), 0.5); + + if (is_noisy) + { + std::fputc('\t', stdout); + std::fflush(stdout); + } +} + +void Critter_update_movement(benchmark::State& st) +{ + for (int i = 0; i < 2; i++) + test_critter(); + for (auto _ : st) + test_critter(); +} + +BENCHMARK(Critter_update_movement)->Unit(benchmark::kMillisecond); + +} // namespace + +} // namespace floormat |