194 lines
6.3 KiB
C++
194 lines
6.3 KiB
C++
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <exception>
|
|
|
|
#include <DynamicOutput/DynamicOutput.hpp>
|
|
#include <Unreal/UObject.hpp>
|
|
|
|
#include <Static/LogGate.hpp>
|
|
|
|
namespace PalUIExtension::Features
|
|
{
|
|
using namespace RC;
|
|
using namespace RC::Unreal;
|
|
|
|
class IUIFeature
|
|
{
|
|
public:
|
|
using StringType = std::basic_string<TCHAR>;
|
|
|
|
virtual ~IUIFeature() = default;
|
|
|
|
virtual auto Name() const -> const TCHAR* = 0;
|
|
|
|
virtual auto InstallHooks() -> void = 0;
|
|
|
|
virtual auto OnWorldTeardown() -> void {}
|
|
|
|
virtual auto SetEnabled(bool enabled) -> void {}
|
|
|
|
virtual auto GetEnabled() -> bool { return false; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---- logging ---------------------------------------------------
|
|
|
|
template <typename... Args>
|
|
auto SendLog(LogLevel::LogLevel Level, const TCHAR* Fmt, Args&&... Fargs) const -> void
|
|
{
|
|
Emit(Level, Fmt, std::forward<Args>(Fargs)...);
|
|
}
|
|
|
|
template <typename... Args>
|
|
auto SendLogOnce(const TCHAR* Key, LogLevel::LogLevel Level, const TCHAR* Fmt, Args&&... Fargs) const -> void
|
|
{
|
|
if (!Key) return;
|
|
if (!m_log_once_keys.insert(StringType{ Key }).second) return;
|
|
|
|
Emit(Level, Fmt, std::forward<Args>(Fargs)...);
|
|
}
|
|
|
|
template <typename... Args>
|
|
auto SendLogThrottled(const TCHAR* Key, int64_t IntervalMs, LogLevel::LogLevel Level, const TCHAR* Fmt, Args&&... Fargs) const -> void
|
|
{
|
|
if (!Key) return;
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
const auto Now = Clock::now();
|
|
|
|
auto& Last = m_log_throttle[StringType{ Key }];
|
|
if (Last.time_since_epoch().count() != 0 && std::chrono::duration_cast<std::chrono::milliseconds>(Now - Last).count() < IntervalMs)
|
|
{
|
|
return;
|
|
}
|
|
Last = Now;
|
|
|
|
Emit(Level, Fmt, std::forward<Args>(Fargs)...);
|
|
}
|
|
|
|
template <typename... Args>
|
|
auto SendVerbose(const TCHAR* Fmt, Args&&... Fargs) const -> void
|
|
{
|
|
if constexpr (Static::kVerboseLogging)
|
|
{
|
|
Emit(LogLevel::Verbose, Fmt, std::forward<Args>(Fargs)...);
|
|
}
|
|
}
|
|
|
|
auto ResetLogOnce() const -> void
|
|
{
|
|
m_log_once_keys.clear();
|
|
m_log_throttle.clear();
|
|
}
|
|
|
|
// ---- argument helpers -------------------------------------------
|
|
static auto NameOf(UObject* Obj) -> StringType { return Obj ? Obj->GetName() : StringType{ STR("<null>") }; }
|
|
|
|
static auto FullNameOf(UObject* Obj) -> StringType { return Obj ? Obj->GetFullName() : StringType{ STR("<null>") }; }
|
|
|
|
static auto ClassNameOf(UObject* Obj) -> StringType
|
|
{
|
|
if (!Obj) return StringType{ STR("<null>") };
|
|
UClass* Cls = Obj->GetClassPrivate();
|
|
return Cls ? Cls->GetName() : StringType{ STR("<no class>") };
|
|
}
|
|
|
|
static auto AddressOf(const void* P) -> uint64_t
|
|
{
|
|
return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(P));
|
|
}
|
|
|
|
private:
|
|
template <typename... Args>
|
|
auto Emit(LogLevel::LogLevel Level, const TCHAR* Fmt, Args&&... Fargs) const -> void
|
|
{
|
|
if (!Fmt) return;
|
|
|
|
try
|
|
{
|
|
switch (Level)
|
|
{
|
|
case LogLevel::Error:
|
|
Output::send<LogLevel::Error>(STR("PalUIExt [{}] "), Name());
|
|
Output::send<LogLevel::Error>(Fmt, std::forward<Args>(Fargs)...);
|
|
Output::send<LogLevel::Error>(STR("\n"));
|
|
break;
|
|
case LogLevel::Warning:
|
|
Output::send<LogLevel::Warning>(STR("PalUIExt [{}] "), Name());
|
|
Output::send<LogLevel::Warning>(Fmt, std::forward<Args>(Fargs)...);
|
|
Output::send<LogLevel::Warning>(STR("\n"));
|
|
break;
|
|
case LogLevel::Verbose:
|
|
Output::send<LogLevel::Verbose>(STR("PalUIExt [{}] "), Name());
|
|
Output::send<LogLevel::Verbose>(Fmt, std::forward<Args>(Fargs)...);
|
|
Output::send<LogLevel::Verbose>(STR("\n"));
|
|
break;
|
|
default:
|
|
Output::send<LogLevel::Normal>(STR("PalUIExt [{}] "), Name());
|
|
Output::send<LogLevel::Normal>(Fmt, std::forward<Args>(Fargs)...);
|
|
Output::send<LogLevel::Normal>(STR("\n"));
|
|
break;
|
|
}
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
try { Output::send<LogLevel::Error>(STR("PalUIExt: malformed log format string\n")); }
|
|
catch (...) {}
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
}
|
|
|
|
mutable std::unordered_set<StringType> m_log_once_keys;
|
|
mutable std::unordered_map<StringType, std::chrono::steady_clock::time_point> m_log_throttle;
|
|
};
|
|
|
|
|
|
class ScopedHotPathTimer
|
|
{
|
|
public:
|
|
ScopedHotPathTimer(const IUIFeature& Feature, const TCHAR* Label,
|
|
int64_t ReportAboveMicros = 250)
|
|
: m_feature(Feature), m_label(Label), m_threshold(ReportAboveMicros)
|
|
{
|
|
if constexpr (Static::kProfileHotPath)
|
|
{
|
|
m_start = std::chrono::steady_clock::now();
|
|
}
|
|
}
|
|
|
|
~ScopedHotPathTimer()
|
|
{
|
|
if constexpr (Static::kProfileHotPath)
|
|
{
|
|
const auto Micros = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
std::chrono::steady_clock::now() - m_start).count();
|
|
|
|
if (Micros >= m_threshold)
|
|
{
|
|
m_feature.SendLogThrottled(m_label, 1000, LogLevel::Warning,
|
|
STR("slow path: {} took {}us"), m_label, Micros);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScopedHotPathTimer(const ScopedHotPathTimer&) = delete;
|
|
ScopedHotPathTimer& operator=(const ScopedHotPathTimer&) = delete;
|
|
|
|
private:
|
|
const IUIFeature& m_feature;
|
|
const TCHAR* m_label;
|
|
int64_t m_threshold;
|
|
std::chrono::steady_clock::time_point m_start{};
|
|
};
|
|
} |