Состояние на момент заведения репозитория. C++ приложение (src/, shaders/, tests/) — минимальный редактор 3D-моделей на Vulkan 1.3: орбитальная камера, три опорные сетки через начало координат, загрузка .obj с режимами отображения. Весь Vulkan изолирован в src/vk/. Исследование (docs/) — оригинальные статьи по KBC (docs/origins) и Python-решатель D2Q9 KBC-N1 с AMR 2x и SDF+Bouzidi (docs/theory). В решателе перед коммитом исправлены дефекты, найденные сверкой с первоисточниками: относительный порог знаменателя энтропийного стабилизатора (абсолютный вырождал KBC в LBGK на 77-99% узлов), заворот вход/выход в углах домена, диагностика средней плотности по фиктивным узлам тела, зашитый refine=2. Подробности — docs/theory/solver_2x_sdf/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
130 lines
4.1 KiB
C++
130 lines
4.1 KiB
C++
#include "Logger.h"
|
|
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <mutex>
|
|
|
|
namespace simv::core {
|
|
|
|
namespace {
|
|
|
|
constexpr std::array<const char*, LogCategoryCount> kCategoryNames = {{
|
|
"core", "vk", "mesh", "ui", "test",
|
|
}};
|
|
static_assert(kCategoryNames.size() == LogCategoryCount,
|
|
"kCategoryNames must have one entry per LogCategory");
|
|
|
|
struct CategoryEntry {
|
|
bool enabled {true};
|
|
LogLevel minLevel {LogLevel::Info};
|
|
double throttleSec{0.5};
|
|
std::atomic<std::int64_t> lastLogMs {INT64_MIN};
|
|
};
|
|
|
|
struct LoggerImpl {
|
|
std::array<CategoryEntry, LogCategoryCount> entries{};
|
|
std::shared_ptr<spdlog::logger> sink;
|
|
std::chrono::steady_clock::time_point epoch{std::chrono::steady_clock::now()};
|
|
std::mutex initMutex;
|
|
bool initialized{false};
|
|
};
|
|
|
|
LoggerImpl& Impl() {
|
|
static LoggerImpl impl;
|
|
return impl;
|
|
}
|
|
|
|
spdlog::level::level_enum ToSpd(LogLevel l) noexcept {
|
|
switch (l) {
|
|
case LogLevel::Trace: return spdlog::level::trace;
|
|
case LogLevel::Debug: return spdlog::level::debug;
|
|
case LogLevel::Info: return spdlog::level::info;
|
|
case LogLevel::Warn: return spdlog::level::warn;
|
|
case LogLevel::Error: return spdlog::level::err;
|
|
case LogLevel::Critical: return spdlog::level::critical;
|
|
default: return spdlog::level::off;
|
|
}
|
|
}
|
|
|
|
std::int64_t NowMs() noexcept {
|
|
using namespace std::chrono;
|
|
return duration_cast<milliseconds>(steady_clock::now() - Impl().epoch).count();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char* CategoryName(LogCategory cat) noexcept {
|
|
const auto idx = static_cast<std::size_t>(cat);
|
|
return idx < LogCategoryCount ? kCategoryNames[idx] : "?";
|
|
}
|
|
|
|
Logger& Logger::Instance() noexcept {
|
|
static Logger inst;
|
|
return inst;
|
|
}
|
|
|
|
void Logger::Init() {
|
|
auto& impl = Impl();
|
|
std::lock_guard<std::mutex> lk(impl.initMutex);
|
|
if (impl.initialized) return;
|
|
|
|
impl.sink = spdlog::stdout_color_mt("simv");
|
|
impl.sink->set_pattern("[%H:%M:%S.%e] [%^%l%$] %v");
|
|
impl.sink->set_level(spdlog::level::trace);
|
|
|
|
impl.initialized = true;
|
|
}
|
|
|
|
void Logger::Shutdown() {
|
|
auto& impl = Impl();
|
|
std::lock_guard<std::mutex> lk(impl.initMutex);
|
|
if (!impl.initialized) return;
|
|
impl.sink.reset();
|
|
spdlog::drop("simv");
|
|
impl.initialized = false;
|
|
}
|
|
|
|
void Logger::SetMinLevel(LogCategory cat, LogLevel lvl) noexcept {
|
|
const auto idx = static_cast<std::size_t>(cat);
|
|
if (idx < LogCategoryCount) Impl().entries[idx].minLevel = lvl;
|
|
}
|
|
|
|
void Logger::SetThrottle(LogCategory cat, double seconds) noexcept {
|
|
const auto idx = static_cast<std::size_t>(cat);
|
|
if (idx < LogCategoryCount) Impl().entries[idx].throttleSec = seconds;
|
|
}
|
|
|
|
void Logger::SetEnabled(LogCategory cat, bool enabled) noexcept {
|
|
const auto idx = static_cast<std::size_t>(cat);
|
|
if (idx < LogCategoryCount) Impl().entries[idx].enabled = enabled;
|
|
}
|
|
|
|
void Logger::LogRaw(LogCategory cat, LogLevel lvl, std::string_view msg) noexcept {
|
|
auto& impl = Impl();
|
|
if (!impl.initialized) Init();
|
|
|
|
const auto idx = static_cast<std::size_t>(cat);
|
|
if (idx >= LogCategoryCount) return;
|
|
auto& e = impl.entries[idx];
|
|
if (!e.enabled) return;
|
|
if (static_cast<std::uint8_t>(lvl) < static_cast<std::uint8_t>(e.minLevel)) return;
|
|
|
|
// Warnings/errors bypass throttling.
|
|
const bool important = (lvl == LogLevel::Warn) || (lvl == LogLevel::Error)
|
|
|| (lvl == LogLevel::Critical);
|
|
if (!important && e.throttleSec > 0.0) {
|
|
const std::int64_t nowMs = NowMs();
|
|
const std::int64_t throttle = static_cast<std::int64_t>(e.throttleSec * 1000.0);
|
|
std::int64_t lastMs = e.lastLogMs.load(std::memory_order_relaxed);
|
|
if (lastMs != INT64_MIN && (nowMs - lastMs) < throttle) return;
|
|
e.lastLogMs.store(nowMs, std::memory_order_relaxed);
|
|
}
|
|
|
|
impl.sink->log(ToSpd(lvl), "[{}] {}", CategoryName(cat), msg);
|
|
}
|
|
|
|
} // namespace simv::core
|