2025-07-22 22:06:34 +03:00

328 lines
11 KiB
C++

#pragma once
// used: [win] winapi
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include "../common.h"
// using: stringcopy, stringcat, timetostring
#include "crt.h"
// used: sdk datatypes
#include "../sdk/datatypes/color.h"
#include "../sdk/datatypes/vector.h"
#include "../sdk/datatypes/qangle.h"
// @todo: poorly designed in case we don't need logging at all, xor continues wasteful compilation without any references | add smth like dummystream_t and unreference macro params?
#pragma region log_definitions
#ifdef _DEBUG
#if defined(CS_COMPILER_CLANG)
#define L_PRINT(LEVEL) L::stream(LEVEL, "[" __FILE_NAME__ ":" CS_STRINGIFY(__LINE__) "] ")
#else
#define L_PRINT(LEVEL) L::stream(LEVEL, L::DETAIL::MakeFileBlock<CRT::StringLength(L::DETAIL::GetFileName(__FILE__)), CRT::StringLength(CS_STRINGIFY(__LINE__))>(L::DETAIL::GetFileName(__FILE__), CS_STRINGIFY(__LINE__)).Get())
#endif
#else
#define L_PRINT(LEVEL) L::stream(LEVEL)
#endif
#pragma endregion
#pragma region log_enumerations
enum ELogLevel : std::uint8_t
{
LOG_NONE = 0,
LOG_INFO,
LOG_WARNING,
LOG_ERROR
};
using LogModeFlags_t = std::uint16_t;
enum ELogModeFlags : LogModeFlags_t
{
LOG_MODE_NONE = 0U,
// boolean formatting
LOG_MODE_BOOL_ALPHA = (1U << 0U), // switches between textual and numeric representation of booleans
// integer formatting
LOG_MODE_INT_SHOWBASE = (1U << 1U), // switches display of number base prefixes used by C++ literal constants
LOG_MODE_INT_FORMAT_HEX = (1U << 2U), // switches integer numbers hexadecimal format
LOG_MODE_INT_FORMAT_DEC = (1U << 3U), // switches integer numbers decimal format
LOG_MODE_INT_FORMAT_OCT = (1U << 4U), // switches integer numbers octal format
LOG_MODE_INT_FORMAT_BIN = (1U << 5U), // switches integer numbers binary format
LOG_MODE_INT_FORMAT_MASK = (LOG_MODE_INT_FORMAT_HEX | LOG_MODE_INT_FORMAT_DEC | LOG_MODE_INT_FORMAT_OCT | LOG_MODE_INT_FORMAT_BIN),
// floating-point formatting
LOG_MODE_FLOAT_SHOWPOINT = (1U << 6U), // switches decimal point for those numbers whose decimal part is zero
LOG_MODE_FLOAT_FORMAT_HEX = (1U << 7U), // switches floating-point numbers hexadecimal format
LOG_MODE_FLOAT_FORMAT_FIXED = (1U << 8U), // switches floating-point numbers formatting in fixed-point notation
LOG_MODE_FLOAT_FORMAT_SCIENTIFIC = (1U << 9U), // switches floating-point numbers formatting in scientific notation
LOG_MODE_FLOAT_FORMAT_MASK = (LOG_MODE_FLOAT_FORMAT_HEX | LOG_MODE_FLOAT_FORMAT_FIXED | LOG_MODE_FLOAT_FORMAT_SCIENTIFIC),
// numerical formatting
LOG_MODE_NUM_SHOWPOSITIVE = (1U << 10U), // switches display of plus sign '+' in non-negative numbers
LOG_MODE_NUM_UPPERCASE = (1U << 11U), // switches uppercase characters in numbers
/* [internal] */
LOG_MODE_REMOVE = (1U << 15U)
};
using LogColorFlags_t = std::uint16_t;
enum ELogColorFlags : LogColorFlags_t
{
LOG_COLOR_FORE_BLUE = FOREGROUND_BLUE,
LOG_COLOR_FORE_GREEN = FOREGROUND_GREEN,
LOG_COLOR_FORE_RED = FOREGROUND_RED,
LOG_COLOR_FORE_INTENSITY = FOREGROUND_INTENSITY,
LOG_COLOR_FORE_GRAY = FOREGROUND_INTENSITY,
LOG_COLOR_FORE_CYAN = FOREGROUND_BLUE | FOREGROUND_GREEN,
LOG_COLOR_FORE_MAGENTA = FOREGROUND_BLUE | FOREGROUND_RED,
LOG_COLOR_FORE_YELLOW = FOREGROUND_GREEN | FOREGROUND_RED,
LOG_COLOR_FORE_BLACK = 0U,
LOG_COLOR_FORE_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
LOG_COLOR_BACK_BLUE = BACKGROUND_BLUE,
LOG_COLOR_BACK_GREEN = BACKGROUND_GREEN,
LOG_COLOR_BACK_RED = BACKGROUND_RED,
LOG_COLOR_BACK_INTENSITY = BACKGROUND_INTENSITY,
LOG_COLOR_BACK_GRAY = BACKGROUND_INTENSITY,
LOG_COLOR_BACK_CYAN = BACKGROUND_BLUE | BACKGROUND_GREEN,
LOG_COLOR_BACK_MAGENTA = BACKGROUND_BLUE | BACKGROUND_RED,
LOG_COLOR_BACK_YELLOW = BACKGROUND_GREEN | BACKGROUND_RED,
LOG_COLOR_BACK_BLACK = 0U,
LOG_COLOR_BACK_WHITE = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
/* [internal] */
LOG_COLOR_DEFAULT = LOG_COLOR_FORE_WHITE | LOG_COLOR_BACK_BLACK
};
#pragma endregion
/*
* LOGGING
* - simple logging system with file and console output
* used for debugging and fetching values/errors at run-time
* @todo: currently not thread safe and can mess messages when used from different threads
*/
namespace L
{
namespace DETAIL
{
// @todo: constructs string per-byte in the stack, how do we can optimize this?
template <std::size_t N>
struct FileBlockStorage_t
{
template <std::size_t... I1, std::size_t... I2>
consteval explicit FileBlockStorage_t(const char* szFileName, const char* szLineNumber, std::index_sequence<I1...>, std::index_sequence<I2...>) :
szStorage{ '[', szFileName[I1]..., ':', szLineNumber[I2]..., ']', ' ', '\0' } { }
[[nodiscard]] constexpr const char* Get() const
{
return szStorage;
}
const char szStorage[N + 5U];
};
// fail-free version of 'StringCharR'
consteval const char* GetFileName(const char* szFilePath)
{
const char* szLastPath = szFilePath;
do
{
if (*szFilePath == '\\')
szLastPath = szFilePath + 1U;
} while (*szFilePath++ != '\0');
return szLastPath;
}
// helper to generate file info block for logging message at compile-time
template <std::size_t N1, std::size_t N2>
consteval auto MakeFileBlock(const char* szFileName, const char* szFileNumber) noexcept
{
return FileBlockStorage_t<N1 + N2>(szFileName, szFileNumber, std::make_index_sequence<N1>{}, std::make_index_sequence<N2>{});
}
}
/* @section: main */
// attach console to current window with write permission and given title
bool AttachConsole(const wchar_t* wszWindowTitle);
// close write streams and detach console from current window
void DetachConsole();
// open logging output file
bool OpenFile(const wchar_t* wszFileName);
// close logging output file
void CloseFile();
// write message to the file or/and console
void WriteMessage(const char* szMessage, const std::size_t nMessageLength);
// alternative of C++ 'std::cout' and other STL-like streams logging scheme
// @todo: is it faster to constantly call 'WriteMessage' instead of concatenating all of the output and print once? i dont think so, due to additional allocations, thread-safe requirements, conditions when we still should call print due to color/etc changes | but generally this should lead to better inlining and less complicated compiled code
struct Stream_t
{
// special unique return type markers to determine and handle change of those flags, just a snap for compile-time, inlined as underlying types at run-time
struct ColorMarker_t
{
LogColorFlags_t nColorFlags;
};
struct PrecisionMarker_t
{
int iPrecision;
};
struct ModeMarker_t
{
LogModeFlags_t nModeFlags;
};
// begin of each log message, puts time, file & line, level blocks
Stream_t& operator()(const ELogLevel nLevel, const char* szFileBlock = nullptr);
// manipulators
Stream_t& operator<<(ColorMarker_t);
Stream_t& operator<<(PrecisionMarker_t);
Stream_t& operator<<(ModeMarker_t);
// message
Stream_t& operator<<(const char* szMessage);
Stream_t& operator<<(const wchar_t* wszMessage);
// conversion
Stream_t& operator<<(const bool bValue);
template <typename T> requires std::is_integral_v<T>
Stream_t& operator<<(const T value)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
int iBase = 10;
const char* szPrefix = nullptr;
if (nModeFlags & LOG_MODE_INT_FORMAT_HEX)
{
iBase = 16;
szPrefix = "0x";
}
else if (nModeFlags & LOG_MODE_INT_FORMAT_OCT)
iBase = 8;
else if (nModeFlags & LOG_MODE_INT_FORMAT_BIN)
{
iBase = 2;
szPrefix = "0b";
}
// @todo: LOG_MODE_NUM_UPPERCASE not handled
char szIntegerBuffer[CRT::IntegerToString_t<std::int64_t, 2U>::MaxCount() + 2U];
char* szInteger = CRT::IntegerToString(value, szIntegerBuffer + 2U, sizeof(szIntegerBuffer) - 2U, iBase);
// @todo: after int2str rework could be simplified | or completely replaced with strformat
if (szPrefix != nullptr && (nModeFlags & LOG_MODE_INT_SHOWBASE))
{
*--szInteger = szPrefix[1];
*--szInteger = szPrefix[0];
}
if constexpr (std::is_signed_v<T>)
{
if (value >= 0 && (nModeFlags & LOG_MODE_NUM_SHOWPOSITIVE))
*--szInteger = '+';
}
const std::size_t nIntegerLength = szIntegerBuffer + sizeof(szIntegerBuffer) - szInteger - 1;
WriteMessage(szInteger, nIntegerLength);
#endif
return *this;
}
template <typename T> requires std::is_floating_point_v<T>
Stream_t& operator<<(const T value)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
//static_assert((nModeFlags & (LOG_MODE_FLOAT_FORMAT_FIXED | LOG_MODE_FLOAT_FORMAT_SCIENTIFIC)) && std::is_same_v<T, float>); // expected 'double' or 'long double'
int iDesiredPrecision = /*((nModeFlags & (LOG_MODE_FLOAT_FORMAT_FIXED | LOG_MODE_FLOAT_FORMAT_SCIENTIFIC)) ? -1 : (*/ iPrecision > 0 ? iPrecision : FLT_DIG; //));
char szFormatBuffer[8];
char* szFormat = szFormatBuffer;
*szFormat++ = '%';
if (nModeFlags & LOG_MODE_NUM_SHOWPOSITIVE)
*szFormat++ = '+';
if (nModeFlags & LOG_MODE_FLOAT_SHOWPOINT)
*szFormat++ = '#';
*szFormat++ = '.';
*szFormat++ = '*';
if constexpr (std::is_same_v<T, long double>)
*szFormat++ = 'L';
if (nModeFlags & LOG_MODE_FLOAT_FORMAT_FIXED)
*szFormat++ = 'f';
else
{
const bool bIsUpperCase = (nModeFlags & LOG_MODE_NUM_UPPERCASE);
if (nModeFlags & LOG_MODE_FLOAT_FORMAT_HEX)
*szFormat++ = bIsUpperCase ? 'A' : 'a';
else if (nModeFlags & LOG_MODE_FLOAT_FORMAT_SCIENTIFIC)
*szFormat++ = bIsUpperCase ? 'E' : 'e';
else
*szFormat++ = bIsUpperCase ? 'G' : 'g';
}
*szFormat = '\0';
char szFloatBuffer[96];
const int nFloatLength = CRT::StringPrintN(szFloatBuffer, sizeof(szFloatBuffer), szFormatBuffer, iDesiredPrecision, value);
WriteMessage(szFloatBuffer, nFloatLength);
#endif
return *this;
}
Stream_t& operator<<(const Vector_t& vecValue)
{
this->nModeFlags |= LOG_MODE_FLOAT_FORMAT_FIXED;
this->iPrecision = 3;
*this << CS_XOR("vector3d: (") << vecValue.x << CS_XOR(" | ") << vecValue.y << CS_XOR(" | ") << vecValue.z << CS_XOR(")");
return *this;
}
Stream_t& operator<<(const QAngle_t& angValue)
{
this->nModeFlags |= LOG_MODE_FLOAT_FORMAT_FIXED;
this->iPrecision = 3;
*this << CS_XOR("qangle: (") << angValue.x << CS_XOR(" | ") << angValue.y << CS_XOR(" | ") << angValue.z << CS_XOR(")");
return *this;
}
Stream_t& operator<<(const Color_t& colValue)
{
*this << CS_XOR("color: (") << static_cast<int>(colValue.r) << CS_XOR(" | ") << static_cast<int>(colValue.g) << CS_XOR(" | ") << static_cast<int>(colValue.b) << CS_XOR(" | ") << static_cast<int>(colValue.a) << CS_XOR(")");
return *this;
}
bool bFirstPrint = true;
int iPrecision = 0;
LogModeFlags_t nModeFlags = LOG_MODE_NONE;
};
/* @section: stream control */
// set console color flags for current stream, will reset on next message
Stream_t::ColorMarker_t SetColor(const LogColorFlags_t nColorFlags);
// set the decimal precision to be used to format floating-point values for current stream, will reset on next message
Stream_t::PrecisionMarker_t SetPrecision(const int m_iPrecision);
// add logging mode flags for current stream, will reset on next message
Stream_t::ModeMarker_t AddFlags(const LogModeFlags_t m_nModeFlags);
// remove logging mode flags for current stream
Stream_t::ModeMarker_t RemoveFlags(const LogModeFlags_t m_nModeFlags);
/* @section: values */
// primary logging stream
inline Stream_t stream;
}