chore(testing): add unit tests and update core render/network pipelines

- add new tests:
  - test_blp_loader.cpp
  - test_dbc_loader.cpp
  - test_entity.cpp
  - test_frustum.cpp
  - test_m2_structs.cpp
  - test_opcode_table.cpp
  - test_packet.cpp
  - test_srp.cpp
  - CMakeLists.txt
- add docs and progress tracking:
  - TESTING.md
  - perf_baseline.md
- update project config/build:
  - .gitignore
  - CMakeLists.txt
  - test.sh
- core engine updates:
  - application.cpp
  - game_handler.cpp
  - world_socket.cpp
  - adt_loader.cpp
  - asset_manager.cpp
  - m2_renderer.cpp
  - post_process_pipeline.cpp
  - renderer.cpp
  - terrain_manager.cpp
  - game_screen.cpp
- add profiler header:
  - profiler.hpp
This commit is contained in:
Paul 2026-04-03 09:41:34 +03:00
parent a2814ab082
commit 2cb47bf126
25 changed files with 2042 additions and 96 deletions

View file

@ -1,5 +1,6 @@
#include "core/application.hpp"
#include "core/coordinates.hpp"
#include "core/profiler.hpp"
#include <unordered_set>
#include <cmath>
#include <chrono>
@ -569,6 +570,7 @@ bool Application::initialize() {
}
void Application::run() {
ZoneScopedN("Application::run");
LOG_INFO("Starting main loop");
// Pin main thread to a dedicated CPU core to reduce scheduling jitter
@ -759,6 +761,7 @@ void Application::run() {
// Update application state
try {
FrameMark;
update(deltaTime);
} catch (const std::bad_alloc& e) {
LOG_ERROR("OOM during Application::update (state=", static_cast<int>(state),
@ -1110,6 +1113,7 @@ void Application::logoutToLogin() {
}
void Application::update(float deltaTime) {
ZoneScopedN("Application::update");
const char* updateCheckpoint = "enter";
try {
// Update based on current state

View file

@ -5304,40 +5304,9 @@ void GameHandler::acceptBattlefield(uint32_t queueSlot) {
// ---------------------------------------------------------------------------
// LFG / Dungeon Finder handlers (WotLK 3.3.5a)
// ---------------------------------------------------------------------------
static const char* lfgJoinResultString(uint8_t result) {
switch (result) {
case 0: return nullptr; // success
case 1: return "Role check failed.";
case 2: return "No LFG slots available for your group.";
case 3: return "No LFG object found.";
case 4: return "No slots available (player).";
case 5: return "No slots available (party).";
case 6: return "Dungeon requirements not met by all members.";
case 7: return "Party members are from different realms.";
case 8: return "Not all members are present.";
case 9: return "Get info timeout.";
case 10: return "Invalid dungeon slot.";
case 11: return "You are marked as a deserter.";
case 12: return "A party member is marked as a deserter.";
case 13: return "You are on a random dungeon cooldown.";
case 14: return "A party member is on a random dungeon cooldown.";
case 16: return "No spec/role available.";
default: return "Cannot join dungeon finder.";
}
}
static const char* lfgTeleportDeniedString(uint8_t reason) {
switch (reason) {
case 0: return "You are not in a LFG group.";
case 1: return "You are not in the dungeon.";
case 2: return "You have a summon pending.";
case 3: return "You are dead.";
case 4: return "You have Deserter.";
case 5: return "You do not meet the requirements.";
default: return "Teleport to dungeon denied.";
}
}
// NOTE: lfgJoinResultString() and lfgTeleportDeniedString() live in
// social_handler.cpp where they are actually called.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// LFG outgoing packets

View file

@ -4,6 +4,7 @@
#include "game/opcode_table.hpp"
#include "auth/crypto.hpp"
#include "core/logger.hpp"
#include "core/profiler.hpp"
#include <iomanip>
#include <sstream>
#include <cstdio>
@ -464,6 +465,7 @@ void WorldSocket::send(const Packet& packet) {
}
void WorldSocket::update() {
ZoneScopedN("WorldSocket::update");
if (!useAsyncPump_) {
pumpNetworkIO();
}

View file

@ -1,5 +1,6 @@
#include "pipeline/adt_loader.hpp"
#include "core/logger.hpp"
#include "core/profiler.hpp"
#include <cstring>
#include <cmath>
#include <algorithm>
@ -28,6 +29,7 @@ float HeightMap::getHeight(int x, int y) const {
// ADTLoader implementation
ADTTerrain ADTLoader::load(const std::vector<uint8_t>& adtData) {
ZoneScopedN("ADTLoader::load");
ADTTerrain terrain;
if (adtData.empty()) {

View file

@ -1,6 +1,7 @@
#include "pipeline/asset_manager.hpp"
#include "core/logger.hpp"
#include "core/memory_monitor.hpp"
#include "core/profiler.hpp"
#include <algorithm>
#include <cstdlib>
#include <filesystem>
@ -166,6 +167,7 @@ void AssetManager::setBaseFallbackPath(const std::string& basePath) {
}
BLPImage AssetManager::loadTexture(const std::string& path) {
ZoneScopedN("AssetManager::loadTexture");
if (!initialized) {
LOG_ERROR("AssetManager not initialized");
return BLPImage();
@ -265,6 +267,7 @@ void AssetManager::setExpansionDataPath(const std::string& path) {
}
std::shared_ptr<DBCFile> AssetManager::loadDBC(const std::string& name) {
ZoneScopedN("AssetManager::loadDBC");
if (!initialized) {
LOG_ERROR("AssetManager not initialized");
return nullptr;

View file

@ -12,6 +12,7 @@
#include "pipeline/asset_manager.hpp"
#include "pipeline/blp_loader.hpp"
#include "core/logger.hpp"
#include "core/profiler.hpp"
#include <chrono>
#include <cctype>
#include <glm/gtc/matrix_transform.hpp>
@ -1866,6 +1867,7 @@ static glm::quat interpQuat(const pipeline::M2AnimationTrack& track,
}
static void computeBoneMatrices(const M2ModelGPU& model, M2Instance& instance) {
ZoneScopedN("M2::computeBoneMatrices");
size_t numBones = std::min(model.bones.size(), size_t(128));
if (numBones == 0) return;
instance.boneMatrices.resize(numBones);
@ -1898,6 +1900,7 @@ static void computeBoneMatrices(const M2ModelGPU& model, M2Instance& instance) {
}
void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::mat4& viewProjection) {
ZoneScopedN("M2Renderer::update");
if (spatialIndexDirty_) {
rebuildSpatialIndex();
}

View file

@ -8,6 +8,7 @@
#include "rendering/camera.hpp"
#include "rendering/amd_fsr3_runtime.hpp"
#include "core/logger.hpp"
#include "core/profiler.hpp"
#include <cstdlib>
#include <algorithm>
#include <glm/gtc/matrix_inverse.hpp>
@ -152,6 +153,7 @@ bool PostProcessPipeline::hasActivePostProcess() const {
bool PostProcessPipeline::executePostProcessing(VkCommandBuffer cmd, uint32_t imageIndex,
Camera* camera, float deltaTime) {
ZoneScopedN("PostProcess::execute");
currentCmd_ = cmd;
camera_ = camera;
lastDeltaTime_ = deltaTime;

View file

@ -13,6 +13,7 @@
#include "rendering/weather.hpp"
#include "rendering/lightning.hpp"
#include "rendering/lighting_manager.hpp"
#include "core/profiler.hpp"
#include "rendering/sky_system.hpp"
#include "rendering/swim_effects.hpp"
#include "rendering/mount_dust.hpp"
@ -797,6 +798,7 @@ void Renderer::applyMsaaChange() {
}
void Renderer::beginFrame() {
ZoneScopedN("Renderer::beginFrame");
if (!vkCtx) return;
if (vkCtx->isDeviceLost()) return;
@ -924,6 +926,7 @@ void Renderer::beginFrame() {
}
void Renderer::endFrame() {
ZoneScopedN("Renderer::endFrame");
if (!vkCtx || currentCmd == VK_NULL_HANDLE) return;
// Track whether a post-processing path switched to an INLINE render pass.
@ -1190,6 +1193,7 @@ bool Renderer::isMoving() const {
}
void Renderer::update(float deltaTime) {
ZoneScopedN("Renderer::update");
globalTime += deltaTime;
if (musicSwitchCooldown_ > 0.0f) {
musicSwitchCooldown_ = std::max(0.0f, musicSwitchCooldown_ - deltaTime);
@ -1975,6 +1979,7 @@ float Renderer::getBrightness() const {
}
void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
ZoneScopedN("Renderer::renderWorld");
(void)world;
// Guard against null command buffer (e.g. after VK_ERROR_DEVICE_LOST)
@ -3042,6 +3047,7 @@ void Renderer::renderReflectionPass() {
}
void Renderer::renderShadowPass() {
ZoneScopedN("Renderer::renderShadowPass");
static const bool skipShadows = (std::getenv("WOWEE_SKIP_SHADOWS") != nullptr);
if (skipShadows) return;
if (!shadowsEnabled || shadowDepthImage[0] == VK_NULL_HANDLE) return;

View file

@ -8,6 +8,7 @@
#include "audio/ambient_sound_manager.hpp"
#include "core/coordinates.hpp"
#include "core/memory_monitor.hpp"
#include "core/profiler.hpp"
#include "pipeline/asset_manager.hpp"
#include "pipeline/adt_loader.hpp"
#include "pipeline/m2_loader.hpp"
@ -188,6 +189,7 @@ bool TerrainManager::initialize(pipeline::AssetManager* assets, TerrainRenderer*
}
void TerrainManager::update(const Camera& camera, float deltaTime) {
ZoneScopedN("TerrainManager::update");
if (!streamingEnabled || !assetManager || !terrainRenderer) {
return;
}
@ -1236,6 +1238,7 @@ void TerrainManager::workerLoop() {
}
void TerrainManager::processReadyTiles() {
ZoneScopedN("TerrainManager::processReadyTiles");
// Move newly ready tiles into the finalizing deque.
// Keep them in pendingTiles so streamTiles() won't re-enqueue them.
{

View file

@ -140,50 +140,8 @@ namespace {
return "Unknown";
}
// Collect all non-comment, non-empty lines from a macro body.
std::vector<std::string> allMacroCommands(const std::string& macroText) {
std::vector<std::string> cmds;
size_t pos = 0;
while (pos <= macroText.size()) {
size_t nl = macroText.find('\n', pos);
std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos);
if (!line.empty() && line.back() == '\r') line.pop_back();
size_t start = line.find_first_not_of(" \t");
if (start != std::string::npos) line = line.substr(start);
if (!line.empty() && line.front() != '#')
cmds.push_back(std::move(line));
if (nl == std::string::npos) break;
pos = nl + 1;
}
return cmds;
}
// Returns the #showtooltip argument from a macro body.
std::string getMacroShowtooltipArg(const std::string& macroText) {
size_t pos = 0;
while (pos <= macroText.size()) {
size_t nl = macroText.find('\n', pos);
std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos);
if (!line.empty() && line.back() == '\r') line.pop_back();
size_t fs = line.find_first_not_of(" \t");
if (fs != std::string::npos) line = line.substr(fs);
if (line.rfind("#showtooltip", 0) == 0 || line.rfind("#show", 0) == 0) {
size_t sp = line.find(' ');
if (sp != std::string::npos) {
std::string arg = line.substr(sp + 1);
size_t as = arg.find_first_not_of(" \t");
if (as != std::string::npos) arg = arg.substr(as);
size_t ae = arg.find_last_not_of(" \t");
if (ae != std::string::npos) arg.resize(ae + 1);
if (!arg.empty()) return arg;
}
return "__auto__";
}
if (nl == std::string::npos) break;
pos = nl + 1;
}
return {};
}
// NOTE: allMacroCommands() and getMacroShowtooltipArg() live in
// action_bar_panel.cpp / chat_panel.cpp where they are actually used.
}
namespace wowee { namespace ui {