From 85c8b5d5f4c13be15c765c5443b03b7477c48224 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 22 Feb 2026 06:32:49 -0800 Subject: [PATCH] Optimize logging overhead and character animation threading --- include/core/logger.hpp | 3 ++ include/rendering/character_renderer.hpp | 3 ++ src/core/logger.cpp | 22 ++++++++++++- src/main.cpp | 18 ++++++++++- src/rendering/character_renderer.cpp | 41 +++++++++++++++++------- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/include/core/logger.hpp b/include/core/logger.hpp index 65f38040..967df38d 100644 --- a/include/core/logger.hpp +++ b/include/core/logger.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace wowee { namespace core { @@ -73,6 +74,8 @@ private: std::mutex mutex; std::ofstream fileStream; bool fileReady = false; + std::chrono::steady_clock::time_point lastFlushTime_{}; + uint32_t flushIntervalMs_ = 250; void ensureFile(); }; diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index 1f06a837..24e0696f 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace wowee { namespace pipeline { class AssetManager; } @@ -269,6 +270,8 @@ private: // Maximum bones supported static constexpr int MAX_BONES = 240; + uint32_t numAnimThreads_ = 1; + std::vector> animFutures_; // Shadow pipeline resources VkPipeline shadowPipeline_ = VK_NULL_HANDLE; diff --git a/src/core/logger.cpp b/src/core/logger.cpp index ddeed740..f58770dc 100644 --- a/src/core/logger.cpp +++ b/src/core/logger.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace wowee { namespace core { @@ -15,9 +16,17 @@ Logger& Logger::getInstance() { void Logger::ensureFile() { if (fileReady) return; fileReady = true; + if (const char* flushMs = std::getenv("WOWEE_LOG_FLUSH_MS")) { + char* end = nullptr; + unsigned long parsed = std::strtoul(flushMs, &end, 10); + if (end != flushMs && parsed <= 10000ul) { + flushIntervalMs_ = static_cast(parsed); + } + } std::error_code ec; std::filesystem::create_directories("logs", ec); fileStream.open("logs/wowee.log", std::ios::out | std::ios::trunc); + lastFlushTime_ = std::chrono::steady_clock::now(); } void Logger::log(LogLevel level, const std::string& message) { @@ -61,7 +70,18 @@ void Logger::log(LogLevel level, const std::string& message) { std::cout << line.str() << '\n'; if (fileStream.is_open()) { fileStream << line.str() << '\n'; - fileStream.flush(); + bool shouldFlush = (level >= LogLevel::WARNING); + if (!shouldFlush) { + auto nowSteady = std::chrono::steady_clock::now(); + auto elapsedMs = std::chrono::duration_cast(nowSteady - lastFlushTime_).count(); + shouldFlush = (elapsedMs >= static_cast(flushIntervalMs_)); + if (shouldFlush) { + lastFlushTime_ = nowSteady; + } + } + if (shouldFlush) { + fileStream.flush(); + } } } diff --git a/src/main.cpp b/src/main.cpp index cbd1c305..97930e30 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,9 @@ #include "core/logger.hpp" #include #include +#include +#include +#include #include #ifdef __linux__ #include @@ -27,6 +30,19 @@ static void crashHandler(int sig) { std::raise(sig); } +static wowee::core::LogLevel readLogLevelFromEnv() { + const char* raw = std::getenv("WOWEE_LOG_LEVEL"); + if (!raw || !*raw) return wowee::core::LogLevel::WARNING; + std::string level(raw); + for (char& c : level) c = static_cast(std::tolower(static_cast(c))); + if (level == "debug") return wowee::core::LogLevel::DEBUG; + if (level == "info") return wowee::core::LogLevel::INFO; + if (level == "warn" || level == "warning") return wowee::core::LogLevel::WARNING; + if (level == "error") return wowee::core::LogLevel::ERROR; + if (level == "fatal") return wowee::core::LogLevel::FATAL; + return wowee::core::LogLevel::WARNING; +} + int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { #ifdef __linux__ g_emergencyDisplay = XOpenDisplay(nullptr); @@ -37,7 +53,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { std::signal(SIGTERM, crashHandler); std::signal(SIGINT, crashHandler); try { - wowee::core::Logger::getInstance().setLogLevel(wowee::core::LogLevel::INFO); + wowee::core::Logger::getInstance().setLogLevel(readLogLevelFromEnv()); LOG_INFO("=== Wowee Native Client ==="); LOG_INFO("Starting application..."); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 67d502e5..c7ab1f10 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram assetManager = am; perFrameLayout_ = perFrameLayout; renderPassOverride_ = renderPassOverride; + numAnimThreads_ = std::max(1u, std::min(8u, std::thread::hardware_concurrency())); VkDevice device = vkCtx_->getDevice(); @@ -1138,21 +1140,32 @@ void CharacterRenderer::update(float deltaTime, const glm::vec3& cameraPos) { } } - int updatedCount = toUpdate.size(); + const size_t updatedCount = toUpdate.size(); - // Thread bone calculations if we have many characters (4+) - if (updatedCount >= 4) { - std::vector> futures; - futures.reserve(updatedCount); + // Thread animation updates in chunks to avoid spawning one task per instance. + if (updatedCount >= 8 && numAnimThreads_ > 1) { + const size_t numThreads = std::min(static_cast(numAnimThreads_), updatedCount); + const size_t chunkSize = updatedCount / numThreads; + const size_t remainder = updatedCount % numThreads; - for (auto& instRef : toUpdate) { - futures.push_back(std::async(std::launch::async, [this, &instRef, deltaTime]() { - updateAnimation(instRef.get(), deltaTime); - })); + animFutures_.clear(); + if (animFutures_.capacity() < numThreads) { + animFutures_.reserve(numThreads); } - // Wait for all to complete - for (auto& f : futures) { + size_t start = 0; + for (size_t t = 0; t < numThreads; t++) { + size_t end = start + chunkSize + (t < remainder ? 1 : 0); + animFutures_.push_back(std::async(std::launch::async, + [this, &toUpdate, start, end, deltaTime]() { + for (size_t i = start; i < end; i++) { + updateAnimation(toUpdate[i].get(), deltaTime); + } + })); + start = end; + } + + for (auto& f : animFutures_) { f.get(); } } else { @@ -1197,7 +1210,11 @@ void CharacterRenderer::update(float deltaTime, const glm::vec3& cameraPos) { } void CharacterRenderer::updateAnimation(CharacterInstance& instance, float deltaTime) { - auto& model = models[instance.modelId].data; + auto modelIt = models.find(instance.modelId); + if (modelIt == models.end()) { + return; + } + const auto& model = modelIt->second.data; if (model.sequences.empty()) { return;