Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Revisions listed here are ignored by `git blame` so that purely
# mechanical commits (reformatting, mass renames) don't overwrite
# the blame history of every line they touch.
#
# GitHub honours this file automatically on the web UI. Locally, run:
#
# git config blame.ignoreRevsFile .git-blame-ignore-revs
#
# to make the local `git blame` respect it as well.
#
# Only add a SHA here when the commit is guaranteed to change no
# semantics -- formatters, whitespace-only edits, renames.

# Repo-wide clang-format-15 sweep (2026-04-23)
101feb407ac0fdfa1b96de37f625b40b716573b0
131 changes: 73 additions & 58 deletions benchmarks/bench_accessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,50 @@

namespace {

// The arena replay lives under samples/content/reader/arena/. Benchmarks
// run from various working dirs, so we resolve the path relative to the
// fixtures dir that CMake exposes via VTX_BENCH_FIXTURES_DIR.
std::string ArenaReplayPath() {
return (std::filesystem::path(VTX_BENCH_FIXTURES_DIR).parent_path().parent_path()
/ "samples" / "content" / "reader" / "arena" / "arena_from_fbs_ds.vtx")
.string();
}
// The arena replay lives under samples/content/reader/arena/. Benchmarks
// run from various working dirs, so we resolve the path relative to the
// fixtures dir that CMake exposes via VTX_BENCH_FIXTURES_DIR.
std::string ArenaReplayPath() {
return (std::filesystem::path(VTX_BENCH_FIXTURES_DIR).parent_path().parent_path() / "samples" / "content" /
"reader" / "arena" / "arena_from_fbs_ds.vtx")
.string();
}

struct SilenceDebugLogsOnce {
SilenceDebugLogsOnce() { VTX::Logger::Instance().SetDebugEnabled(false); }
};
const SilenceDebugLogsOnce silence_accessor_debug_logs_once{};

// Small bundle that holds the reader + accessor + resolved keys. Moving
// this construction out of the measured loop is intentional -- a real
// integration resolves keys once at setup, not per frame.
struct AccessorFixture {
VTX::ReaderContext reader_result;
VTX::FrameAccessor accessor;
VTX::PropertyKey<VTX::Vector> key_position;
VTX::PropertyKey<float> key_health;
VTX::PropertyKey<std::string> key_unique_id;

static AccessorFixture Load(benchmark::State& state) {
AccessorFixture f;
f.reader_result = VTX::OpenReplayFile(ArenaReplayPath());
if (!f.reader_result) {
state.SkipWithError("OpenReplayFile failed (run vtx_sample_generate + vtx_sample_advance_write first)");
return f;
}
f.accessor = f.reader_result.reader->CreateAccessor();
f.key_position = f.accessor.Get<VTX::Vector>("Player", "Position");
f.key_health = f.accessor.Get<float>("Player", "Health");
f.key_unique_id = f.accessor.Get<std::string>("Player", "UniqueID");
struct SilenceDebugLogsOnce {
SilenceDebugLogsOnce() { VTX::Logger::Instance().SetDebugEnabled(false); }
};
const SilenceDebugLogsOnce silence_accessor_debug_logs_once {};

// Small bundle that holds the reader + accessor + resolved keys. Moving
// this construction out of the measured loop is intentional -- a real
// integration resolves keys once at setup, not per frame.
struct AccessorFixture {
VTX::ReaderContext reader_result;
VTX::FrameAccessor accessor;
VTX::PropertyKey<VTX::Vector> key_position;
VTX::PropertyKey<float> key_health;
VTX::PropertyKey<std::string> key_unique_id;

static AccessorFixture Load(benchmark::State& state) {
AccessorFixture f;
f.reader_result = VTX::OpenReplayFile(ArenaReplayPath());
if (!f.reader_result) {
state.SkipWithError("OpenReplayFile failed (run vtx_sample_generate + vtx_sample_advance_write first)");
return f;
}
f.accessor = f.reader_result.reader->CreateAccessor();
f.key_position = f.accessor.Get<VTX::Vector>("Player", "Position");
f.key_health = f.accessor.Get<float>("Player", "Health");
f.key_unique_id = f.accessor.Get<std::string>("Player", "UniqueID");

if (!f.key_position.IsValid() || !f.key_health.IsValid() || !f.key_unique_id.IsValid()) {
state.SkipWithError("one or more Player keys did not resolve");
if (!f.key_position.IsValid() || !f.key_health.IsValid() || !f.key_unique_id.IsValid()) {
state.SkipWithError("one or more Player keys did not resolve");
}
return f;
}
return f;
}
};
};

} // namespace
} // namespace

// Open the replay, create the accessor, resolve three keys, then linearly
// enumerate every frame, iterate every entity in the "entity" bucket, and
Expand All @@ -92,24 +92,27 @@ static void BM_AccessorSequentialScan(benchmark::State& state) {
int32_t total_frames = 0;
for (auto _ : state) {
auto fixture = AccessorFixture::Load(state);
if (!fixture.reader_result) break;
if (!fixture.reader_result)
break;
auto& reader = fixture.reader_result.reader;

total_frames = reader->GetTotalFrames();
double pos_accum = 0.0;
float hp_accum = 0.0f;
size_t id_count = 0;
float hp_accum = 0.0f;
size_t id_count = 0;

for (int32_t i = 0; i < total_frames; ++i) {
const auto* frame = reader->GetFrameSync(i);
if (!frame) continue;
if (!frame)
continue;
for (const auto& bucket : frame->GetBuckets()) {
for (const auto& entity : bucket.entities) {
if (entity.entity_type_id != 0) continue; // 0 = Player
if (entity.entity_type_id != 0)
continue; // 0 = Player
VTX::EntityView view(entity);
pos_accum += view.Get(fixture.key_position).x;
hp_accum += view.Get(fixture.key_health);
id_count += view.Get(fixture.key_unique_id).size();
hp_accum += view.Get(fixture.key_health);
id_count += view.Get(fixture.key_unique_id).size();
}
}
}
Expand All @@ -128,12 +131,15 @@ BENCHMARK(BM_AccessorSequentialScan)->Unit(benchmark::kMillisecond);
// the replay in memory would observe when scrubbing over the properties.
static void BM_AccessorHotLoopPreloaded(benchmark::State& state) {
auto result = VTX::OpenReplayFile(ArenaReplayPath());
if (!result) { state.SkipWithError("OpenReplayFile failed"); return; }
if (!result) {
state.SkipWithError("OpenReplayFile failed");
return;
}
auto& reader = result.reader;

auto accessor = reader->CreateAccessor();
auto accessor = reader->CreateAccessor();
auto key_position = accessor.Get<VTX::Vector>("Player", "Position");
auto key_health = accessor.Get<float>("Player", "Health");
auto key_health = accessor.Get<float>("Player", "Health");
if (!key_position.IsValid() || !key_health.IsValid()) {
state.SkipWithError("keys did not resolve");
return;
Expand Down Expand Up @@ -161,7 +167,8 @@ static void BM_AccessorHotLoopPreloaded(benchmark::State& state) {
for (const auto& frame : ram_cache) {
for (const auto& bucket : frame.GetBuckets()) {
for (const auto& entity : bucket.entities) {
if (entity.entity_type_id == 0) ++entities_per_sweep;
if (entity.entity_type_id == 0)
++entities_per_sweep;
}
}
}
Expand All @@ -171,7 +178,8 @@ static void BM_AccessorHotLoopPreloaded(benchmark::State& state) {
for (const auto& frame : ram_cache) {
for (const auto& bucket : frame.GetBuckets()) {
for (const auto& entity : bucket.entities) {
if (entity.entity_type_id != 0) continue;
if (entity.entity_type_id != 0)
continue;
VTX::EntityView view(entity);
sink += view.Get(key_position).x + view.Get(key_health);
}
Expand All @@ -189,10 +197,13 @@ BENCHMARK(BM_AccessorHotLoopPreloaded)->Unit(benchmark::kMillisecond);
// behaviour under non-sequential access.
static void BM_AccessorRandomWithinBucket(benchmark::State& state) {
auto result = VTX::OpenReplayFile(ArenaReplayPath());
if (!result) { state.SkipWithError("OpenReplayFile failed"); return; }
if (!result) {
state.SkipWithError("OpenReplayFile failed");
return;
}
auto& reader = result.reader;

auto accessor = reader->CreateAccessor();
auto accessor = reader->CreateAccessor();
auto key_position = accessor.Get<VTX::Vector>("Player", "Position");
if (!key_position.IsValid()) {
state.SkipWithError("Player::Position did not resolve");
Expand Down Expand Up @@ -227,16 +238,20 @@ static void BM_AccessorRandomWithinBucket(benchmark::State& state) {
std::vector<size_t> idxs;
idxs.reserve(bucket.entities.size());
for (size_t i = 0; i < bucket.entities.size(); ++i) {
if (bucket.entities[i].entity_type_id == 0) idxs.push_back(i);
if (bucket.entities[i].entity_type_id == 0) idxs.push_back(i);
if (bucket.entities[i].entity_type_id == 0)
idxs.push_back(i);
if (bucket.entities[i].entity_type_id == 0)
idxs.push_back(i);
}
if (idxs.size() < 2) continue;
if (idxs.size() < 2)
continue;
std::shuffle(idxs.begin(), idxs.end(), rng);
prepared.push_back({&bucket.entities, std::move(idxs)});
}
}
int64_t ops_per_sweep = 0;
for (const auto& pb : prepared) ops_per_sweep += static_cast<int64_t>(pb.shuffled.size());
for (const auto& pb : prepared)
ops_per_sweep += static_cast<int64_t>(pb.shuffled.size());

double sink = 0.0;
for (auto _ : state) {
Expand Down
54 changes: 30 additions & 24 deletions benchmarks/bench_accessor_key_resolution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,45 @@

namespace {

std::string ArenaReplayPath() {
return (std::filesystem::path(VTX_BENCH_FIXTURES_DIR).parent_path().parent_path()
/ "samples" / "content" / "reader" / "arena" / "arena_from_fbs_ds.vtx")
.string();
}
std::string ArenaReplayPath() {
return (std::filesystem::path(VTX_BENCH_FIXTURES_DIR).parent_path().parent_path() / "samples" / "content" /
"reader" / "arena" / "arena_from_fbs_ds.vtx")
.string();
}

struct SilenceDebugLogsOnce {
SilenceDebugLogsOnce() { VTX::Logger::Instance().SetDebugEnabled(false); }
};
const SilenceDebugLogsOnce silence_keyres_debug_logs_once{};
struct SilenceDebugLogsOnce {
SilenceDebugLogsOnce() { VTX::Logger::Instance().SetDebugEnabled(false); }
};
const SilenceDebugLogsOnce silence_keyres_debug_logs_once {};

// A representative spread of property lookups a real integration performs.
struct PropSpec { const char* struct_name; const char* prop_name; };
constexpr std::array<PropSpec, 9> kProps = {{
{"Player", "Position"},
{"Player", "Health"},
{"Player", "UniqueID"},
{"Player", "Velocity"},
{"Player", "Score"},
{"Projectile", "Position"},
{"Projectile", "Damage"},
{"MatchState", "ScoreTeam1"},
{"MatchState", "ScoreTeam2"},
}};
// A representative spread of property lookups a real integration performs.
struct PropSpec {
const char* struct_name;
const char* prop_name;
};
constexpr std::array<PropSpec, 9> kProps = {{
{"Player", "Position"},
{"Player", "Health"},
{"Player", "UniqueID"},
{"Player", "Velocity"},
{"Player", "Score"},
{"Projectile", "Position"},
{"Projectile", "Damage"},
{"MatchState", "ScoreTeam1"},
{"MatchState", "ScoreTeam2"},
}};

} // namespace
} // namespace

// Resolve a full set of PropertyKey<T> via the public accessor API. If
// the underlying cache is O(1) then time-per-resolution should stay
// constant regardless of how many properties the schema defines.
static void BM_AccessorKeyResolution(benchmark::State& state) {
auto result = VTX::OpenReplayFile(ArenaReplayPath());
if (!result) { state.SkipWithError("OpenReplayFile failed"); return; }
if (!result) {
state.SkipWithError("OpenReplayFile failed");
return;
}

const auto accessor = result.reader->CreateAccessor();

Expand Down
Loading
Loading