24ms average on WINDOWS is wild

This commit is contained in:
Mars 2025-05-05 01:41:50 -04:00
parent 7e7678f5f0
commit 0027fa6520
22 changed files with 627 additions and 529 deletions

View file

@ -25,24 +25,33 @@ namespace util::cache {
* Should ideally only contain filesystem-safe characters.
* @return Result containing the filesystem path on success, or a DracError on failure.
*/
inline fn GetCachePath(const String& cache_key) -> Result<fs::path, DracError> {
inline fn GetCachePath(const String& cache_key) -> Result<fs::path> {
if (cache_key.empty())
return Err(DracError(DracErrorCode::InvalidArgument, "Cache key cannot be empty."));
// Basic check for potentially problematic characters in the key for filename safety.
// You might want to expand this or implement more robust sanitization if needed.
if (cache_key.find_first_of("/\\:*?\"<>|") != String::npos)
return Err(
DracError(DracErrorCode::InvalidArgument, std::format("Cache key '{}' contains invalid characters.", cache_key))
);
std::error_code errc;
const fs::path cacheDir = fs::temp_directory_path(errc);
const fs::path cacheDir = fs::temp_directory_path(errc) / "draconis++";
if (!fs::exists(cacheDir, errc)) {
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to check existence of cache directory: " + errc.message())
);
fs::create_directories(cacheDir, errc);
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to create cache directory: " + errc.message()));
}
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to get system temporary directory: " + errc.message()));
// Use a consistent naming scheme
return cacheDir / (cache_key + "_cache.beve");
}
@ -53,18 +62,17 @@ namespace util::cache {
* @return Result containing the deserialized object of type T on success, or a DracError on failure.
*/
template <typename T>
fn ReadCache(const String& cache_key) -> Result<T, DracError> {
Result<fs::path, DracError> cachePathResult = GetCachePath(cache_key);
fn ReadCache(const String& cache_key) -> Result<T> {
Result<fs::path> cachePathResult = GetCachePath(cache_key);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
if (std::error_code existsEc; !fs::exists(cachePath, existsEc) || existsEc) {
if (existsEc) {
// Log if there was an error checking existence, but still return NotFound
if (existsEc)
warn_log("Error checking existence of cache file '{}': {}", cachePath.string(), existsEc.message());
}
return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string()));
}
@ -72,17 +80,13 @@ namespace util::cache {
if (!ifs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open cache file for reading: " + cachePath.string()));
debug_log("Reading cache for key '{}' from: {}", cache_key, cachePath.string());
try {
// Read the entire file content
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close(); // Close the file as soon as content is read
ifs.close();
if (content.empty())
return Err(DracError(DracErrorCode::ParseError, "BEVE cache file is empty: " + cachePath.string()));
// Ensure T is default constructible for Glaze
static_assert(std::is_default_constructible_v<T>, "Cache type T must be default constructible for Glaze.");
T result {};
@ -98,14 +102,12 @@ namespace util::cache {
));
}
debug_log("Successfully read cache for key '{}'.", cache_key);
return result;
} catch (const std::ios_base::failure& e) {
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error reading cache file {}: {}", cachePath.string(), e.what())
));
} catch (const Exception& e) {
// Catching std::exception or a project-specific base exception
return Err(DracError(
DracErrorCode::InternalError,
std::format("Standard exception reading cache file {}: {}", cachePath.string(), e.what())
@ -123,24 +125,20 @@ namespace util::cache {
* @return Result containing void on success, or a DracError on failure.
*/
template <typename T>
fn WriteCache(const String& cache_key, const T& data) -> Result<void, DracError> {
Result<fs::path, DracError> cachePathResult = GetCachePath(cache_key);
fn WriteCache(const String& cache_key, const T& data) -> Result<> {
Result<fs::path> cachePathResult = GetCachePath(cache_key);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
fs::path tempPath = cachePath;
tempPath += ".tmp"; // Use a temporary file for atomic write
debug_log("Writing cache for key '{}' to: {}", cache_key, cachePath.string());
tempPath += ".tmp";
try {
String binaryBuffer;
// Use Glaze to serialize
// Need to decay T in case it's a reference type from the caller
using DecayedT = std::decay_t<T>;
DecayedT dataToSerialize = data; // Make a copy if needed for non-const Glaze operations
DecayedT dataToSerialize = data;
if (glz::error_ctx glazeErr = glz::write_beve(dataToSerialize, binaryBuffer); glazeErr) {
return Err(DracError(
@ -154,7 +152,6 @@ namespace util::cache {
));
}
// Scope for ofstream to ensure it's closed before rename
{
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open())
@ -163,22 +160,18 @@ namespace util::cache {
ofs.write(binaryBuffer.data(), static_cast<isize>(binaryBuffer.size()));
if (!ofs) {
// Attempt cleanup before returning error
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt
fs::remove(tempPath, removeEc);
return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string())
);
}
// ofstream automatically closed here
}
// Attempt to atomically replace the old cache file
std::error_code renameEc;
fs::rename(tempPath, cachePath, renameEc);
if (renameEc) {
// If rename failed, attempt to clean up the temp file
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt
fs::remove(tempPath, removeEc);
return Err(DracError(
DracErrorCode::IoError,
std::format(
@ -190,26 +183,24 @@ namespace util::cache {
));
}
debug_log("Successfully wrote cache for key '{}'.", cache_key);
return {}; // Success
return {};
} catch (const std::ios_base::failure& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
fs::remove(tempPath, removeEc);
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (const Exception& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
fs::remove(tempPath, removeEc);
return Err(DracError(
DracErrorCode::InternalError,
std::format("Standard exception writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (...) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
fs::remove(tempPath, removeEc);
return Err(DracError(DracErrorCode::Other, "Unknown error writing cache file: " + tempPath.string()));
}
}
} // namespace util::cache

View file

@ -1,6 +1,6 @@
#pragma once
#include <format>
#include <expected> // std::{unexpected, expected}
#include <source_location> // std::source_location
#include <system_error> // std::error_code
@ -11,115 +11,146 @@
#include "src/util/types.hpp"
namespace util::error {
using types::u8, types::i32, types::String, types::StringView, types::Exception;
#include "include/matchit.h"
/**
* @enum DracErrorCode
* @brief Error codes for general OS-level operations.
*/
enum class DracErrorCode : u8 {
ApiUnavailable, ///< A required OS service/API is unavailable or failed unexpectedly at runtime.
InternalError, ///< An error occurred within the application's OS abstraction code logic.
InvalidArgument, ///< An invalid argument was passed to a function or method.
IoError, ///< General I/O error (filesystem, pipes, etc.).
NetworkError, ///< A network-related error occurred (e.g., DNS resolution, connection failure).
NotFound, ///< A required resource (file, registry key, device, API endpoint) was not found.
NotSupported, ///< The requested operation is not supported on this platform, version, or configuration.
Other, ///< A generic or unclassified error originating from the OS or an external library.
OutOfMemory, ///< The system ran out of memory or resources to complete the operation.
ParseError, ///< Failed to parse data obtained from the OS (e.g., file content, API output).
PermissionDenied, ///< Insufficient permissions to perform the operation.
PlatformSpecific, ///< An unmapped error specific to the underlying OS platform occurred (check message).
Timeout, ///< An operation timed out (e.g., waiting for IPC reply).
};
namespace util {
namespace error {
using types::u8, types::i32, types::String, types::StringView, types::Exception;
/**
* @struct DracError
* @brief Holds structured information about an OS-level error.
*
* Used as the error type in Result for many os:: functions.
*/
struct DracError {
// ReSharper disable CppDFANotInitializedField
String message; ///< A descriptive error message, potentially including platform details.
DracErrorCode code; ///< The general category of the error.
std::source_location location; ///< The source location where the error occurred (file, line, function).
// ReSharper restore CppDFANotInitializedField
/**
* @enum DracErrorCode
* @brief Error codes for general OS-level operations.
*/
enum class DracErrorCode : u8 {
ApiUnavailable, ///< A required OS service/API is unavailable or failed unexpectedly at runtime.
InternalError, ///< An error occurred within the application's OS abstraction code logic.
InvalidArgument, ///< An invalid argument was passed to a function or method.
IoError, ///< General I/O error (filesystem, pipes, etc.).
NetworkError, ///< A network-related error occurred (e.g., DNS resolution, connection failure).
NotFound, ///< A required resource (file, registry key, device, API endpoint) was not found.
NotSupported, ///< The requested operation is not supported on this platform, version, or configuration.
Other, ///< A generic or unclassified error originating from the OS or an external library.
OutOfMemory, ///< The system ran out of memory or resources to complete the operation.
ParseError, ///< Failed to parse data obtained from the OS (e.g., file content, API output).
PermissionDenied, ///< Insufficient permissions to perform the operation.
PlatformSpecific, ///< An unmapped error specific to the underlying OS platform occurred (check message).
Timeout, ///< An operation timed out (e.g., waiting for IPC reply).
};
DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current())
: message(std::move(msg)), code(errc), location(loc) {}
/**
* @struct DracError
* @brief Holds structured information about an OS-level error.
*
* Used as the error type in Result for many os:: functions.
*/
struct DracError {
// ReSharper disable CppDFANotInitializedField
String message; ///< A descriptive error message, potentially including platform details.
DracErrorCode code; ///< The general category of the error.
std::source_location location; ///< The source location where the error occurred (file, line, function).
// ReSharper restore CppDFANotInitializedField
explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current())
: message(exc.what()), code(DracErrorCode::InternalError), location(loc) {}
DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current())
: message(std::move(msg)), code(errc), location(loc) {}
explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current())
: message(errc.message()), location(loc) {
using enum DracErrorCode;
using enum std::errc;
explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current())
: message(exc.what()), code(DracErrorCode::InternalError), location(loc) {}
switch (static_cast<std::errc>(errc.value())) {
case permission_denied: code = PermissionDenied; break;
case no_such_file_or_directory: code = NotFound; break;
case timed_out: code = Timeout; break;
case io_error: code = IoError; break;
case network_unreachable:
case network_down:
case connection_refused: code = NetworkError; break;
case not_supported: code = NotSupported; break;
default: code = errc.category() == std::generic_category() ? InternalError : PlatformSpecific; break;
explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current())
: message(errc.message()), location(loc) {
using namespace matchit;
using enum DracErrorCode;
using enum std::errc;
code = match(errc)(
is | or_(file_too_large, io_error) = IoError,
is | invalid_argument = InvalidArgument,
is | not_enough_memory = OutOfMemory,
is | or_(address_family_not_supported, operation_not_supported, not_supported) = NotSupported,
is | or_(network_unreachable, network_down, connection_refused) = NetworkError,
is | or_(no_such_file_or_directory, not_a_directory, is_a_directory, file_exists) = NotFound,
is | permission_denied = PermissionDenied,
is | timed_out = Timeout,
is | _ = errc.category() == std::generic_category() ? InternalError : PlatformSpecific
);
}
}
#ifdef _WIN32
explicit DracError(const winrt::hresult_error& e) : message(winrt::to_string(e.message())) {
switch (e.code()) {
case E_ACCESSDENIED: code = DracErrorCode::PermissionDenied; break;
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND):
case HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_FOUND): code = DracErrorCode::NotFound; break;
case HRESULT_FROM_WIN32(ERROR_TIMEOUT):
case HRESULT_FROM_WIN32(ERROR_SEM_TIMEOUT): code = DracErrorCode::Timeout; break;
case HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED): code = DracErrorCode::NotSupported; break;
default: code = DracErrorCode::PlatformSpecific; break;
explicit DracError(const winrt::hresult_error& e) : message(winrt::to_string(e.message())) {
using namespace matchit;
using enum DracErrorCode;
fn fromWin32 = [](const types::u32 x) -> HRESULT { return HRESULT_FROM_WIN32(x); };
code = match(e.code())(
is | or_(E_ACCESSDENIED, fromWin32(ERROR_ACCESS_DENIED)) = PermissionDenied,
is | fromWin32(ERROR_FILE_NOT_FOUND) = NotFound,
is | fromWin32(ERROR_PATH_NOT_FOUND) = NotFound,
is | fromWin32(ERROR_SERVICE_NOT_FOUND) = NotFound,
is | fromWin32(ERROR_TIMEOUT) = Timeout,
is | fromWin32(ERROR_SEM_TIMEOUT) = Timeout,
is | fromWin32(ERROR_NOT_SUPPORTED) = NotSupported,
is | _ = PlatformSpecific
);
}
}
#else
DracError(const DracErrorCode code_hint, const int errno_val)
: message(std::system_category().message(errno_val)), code(code_hint) {
using enum DracErrorCode;
DracError(const DracErrorCode code_hint, const int errno_val)
: message(std::system_category().message(errno_val)), code(code_hint) {
using enum DracErrorCode;
switch (errno_val) {
case EACCES: code = PermissionDenied; break;
case ENOENT: code = NotFound; break;
case ETIMEDOUT: code = Timeout; break;
case ENOTSUP: code = NotSupported; break;
default: code = PlatformSpecific; break;
}
}
static auto withErrno(const String& context, const std::source_location& loc = std::source_location::current())
-> DracError {
const i32 errNo = errno;
const String msg = std::system_category().message(errNo);
const String fullMsg = std::format("{}: {}", context, msg);
const DracErrorCode code = [&errNo] {
switch (errNo) {
case EACCES:
case EPERM: return DracErrorCode::PermissionDenied;
case ENOENT: return DracErrorCode::NotFound;
case ETIMEDOUT: return DracErrorCode::Timeout;
case ENOTSUP: return DracErrorCode::NotSupported;
case EIO: return DracErrorCode::IoError;
case ECONNREFUSED:
case ENETDOWN:
case ENETUNREACH: return DracErrorCode::NetworkError;
default: return DracErrorCode::PlatformSpecific;
switch (errno_val) {
case EACCES: code = PermissionDenied; break;
case ENOENT: code = NotFound; break;
case ETIMEDOUT: code = Timeout; break;
case ENOTSUP: code = NotSupported; break;
default: code = PlatformSpecific; break;
}
}();
}
return DracError { code, fullMsg, loc };
}
static auto withErrno(const String& context, const std::source_location& loc = std::source_location::current())
-> DracError {
const i32 errNo = errno;
const String msg = std::system_category().message(errNo);
const String fullMsg = std::format("{}: {}", context, msg);
const DracErrorCode code = [&errNo] {
switch (errNo) {
case EACCES:
case EPERM: return DracErrorCode::PermissionDenied;
case ENOENT: return DracErrorCode::NotFound;
case ETIMEDOUT: return DracErrorCode::Timeout;
case ENOTSUP: return DracErrorCode::NotSupported;
case EIO: return DracErrorCode::IoError;
case ECONNREFUSED:
case ENETDOWN:
case ENETUNREACH: return DracErrorCode::NetworkError;
default: return DracErrorCode::PlatformSpecific;
}
}();
return DracError { code, fullMsg, loc };
}
#endif
};
} // namespace util::error
};
} // namespace error
namespace types {
/**
* @typedef Result
* @brief Alias for std::expected<Tp, Er>. Represents a value that can either be
* a success value of type Tp or an error value of type Er.
* @tparam Tp The type of the success value.
* @tparam Er The type of the error value.
*/
template <typename Tp = void, typename Er = error::DracError>
using Result = std::expected<Tp, Er>;
/**
* @typedef Err
* @brief Alias for std::unexpected<Er>. Used to construct a Result in an error state.
* @tparam Er The type of the error value.
*/
template <typename Er = error::DracError>
using Err = std::unexpected<Er>;
} // namespace types
} // namespace util

View file

@ -14,7 +14,7 @@ namespace util::helpers {
* @return A Result containing the value of the environment variable as a String,
* or an EnvError if an error occurred.
*/
[[nodiscard]] inline fn GetEnv(CStr name) -> Result<String, DracError> {
[[nodiscard]] inline fn GetEnv(CStr name) -> Result<String> {
#ifdef _WIN32
using types::i32, types::usize, types::UniquePointer;

View file

@ -5,10 +5,15 @@
#include <filesystem> // std::filesystem::path
#include <format> // std::format
#include <ftxui/screen/color.hpp> // ftxui::Color
#include <iostream> // std::cout
#include <mutex> // std::{mutex, lock_guard}
#include <utility> // std::forward
#ifdef __cpp_lib_print
#include <print> // std::print
#else
#include <iostream> // std::cout
#endif
#ifndef NDEBUG
#include <source_location> // std::source_location
#endif
@ -17,6 +22,8 @@
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#include "ftxui/dom/elements.hpp"
namespace util::logging {
using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array,
types::Option, types::None, types::Mutex, types::LockGuard;
@ -118,13 +125,15 @@ namespace util::logging {
* @return FTXUI color code
*/
constexpr fn GetLevelColor(const LogLevel level) -> ftxui::Color::Palette16 {
switch (level) {
case LogLevel::Debug: return LogLevelConst::DEBUG_COLOR;
case LogLevel::Info: return LogLevelConst::INFO_COLOR;
case LogLevel::Warn: return LogLevelConst::WARN_COLOR;
case LogLevel::Error: return LogLevelConst::ERROR_COLOR;
default: std::unreachable();
}
using namespace matchit;
using enum LogLevel;
return match(level)(
is | Debug = LogLevelConst::DEBUG_COLOR,
is | Info = LogLevelConst::INFO_COLOR,
is | Warn = LogLevelConst::WARN_COLOR,
is | Error = LogLevelConst::ERROR_COLOR
);
}
/**
@ -133,13 +142,15 @@ namespace util::logging {
* @return String representation
*/
constexpr fn GetLevelString(const LogLevel level) -> StringView {
switch (level) {
case LogLevel::Debug: return LogLevelConst::DEBUG_STR;
case LogLevel::Info: return LogLevelConst::INFO_STR;
case LogLevel::Warn: return LogLevelConst::WARN_STR;
case LogLevel::Error: return LogLevelConst::ERROR_STR;
default: std::unreachable();
}
using namespace matchit;
using enum LogLevel;
return match(level)(
is | Debug = LogLevelConst::DEBUG_STR,
is | Info = LogLevelConst::INFO_STR,
is | Warn = LogLevelConst::WARN_STR,
is | Error = LogLevelConst::ERROR_STR
);
}
// ReSharper disable once CppDoxygenUnresolvedReference
@ -180,7 +191,8 @@ namespace util::logging {
if (localtime_r(&nowTt, &localTm) != nullptr) {
#endif
Array<char, 64> timeBuffer {};
auto formattedTime =
const usize formattedTime =
std::strftime(timeBuffer.data(), sizeof(timeBuffer), LogLevelConst::TIMESTAMP_FORMAT, &localTm);
if (formattedTime > 0) {
@ -188,11 +200,10 @@ namespace util::logging {
} else {
try {
timestamp = std::format("{:%X}", nowTp);
} catch (const std::format_error& fmt_err) { timestamp = "??:??:?? (fmt_err)"; }
} catch ([[maybe_unused]] const std::format_error& fmtErr) { timestamp = "??:??:??"; }
}
} else {
timestamp = "??:??:?? (conv_err)";
}
} else
timestamp = "??:??:??";
const String message = std::format(fmt, std::forward<Args>(args)...);
@ -203,16 +214,28 @@ namespace util::logging {
message
);
#ifdef __cpp_lib_print
std::print("{}", mainLogLine);
#else
std::cout << mainLogLine;
#endif
#ifndef NDEBUG
const String fileLine =
std::format(LogLevelConst::FILE_LINE_FORMAT, path(loc.file_name()).lexically_normal().string(), loc.line());
const String fullDebugLine = std::format("{}{}", LogLevelConst::DEBUG_LINE_PREFIX, fileLine);
#ifdef __cpp_lib_print
std::print("\n{}", Italic(Colorize(fullDebugLine, LogLevelConst::DEBUG_INFO_COLOR)));
#else
std::cout << '\n' << Italic(Colorize(fullDebugLine, LogLevelConst::DEBUG_INFO_COLOR));
#endif
#endif
#ifdef __cpp_lib_print
std::println("{}", LogLevelConst::RESET_CODE);
#else
std::cout << LogLevelConst::RESET_CODE << '\n';
#endif
}
template <typename ErrorType>

View file

@ -1,7 +1,6 @@
#pragma once
#include <array> // std::array (Array)
#include <expected> // std::expected (Result)
#include <future> // std::future (Future)
#include <map> // std::map (Map)
#include <memory> // std::shared_ptr and std::unique_ptr (SharedPointer, UniquePointer)
@ -12,6 +11,8 @@
#include <utility> // std::pair (Pair)
#include <vector> // std::vector (Vec)
#include "include/matchit.h"
namespace util::types {
using u8 = std::uint8_t; ///< 8-bit unsigned integer.
using u16 = std::uint16_t; ///< 16-bit unsigned integer.
@ -40,24 +41,6 @@ namespace util::types {
inline constexpr std::nullopt_t None = std::nullopt; ///< Represents an empty optional value.
/**
* @typedef Result
* @brief Alias for std::expected<Tp, Er>. Represents a value that can either be
* a success value of type Tp or an error value of type Er.
* @tparam Tp The type of the success value.
* @tparam Er The type of the error value.
*/
template <typename Tp, typename Er>
using Result = std::expected<Tp, Er>;
/**
* @typedef Err
* @brief Alias for std::unexpected<Er>. Used to construct a Result in an error state.
* @tparam Er The type of the error value.
*/
template <typename Er>
using Err = std::unexpected<Er>;
/**
* @typedef Option
* @brief Alias for std::optional<Tp>. Represents a value that may or may not be present.
@ -133,8 +116,8 @@ namespace util::types {
* Used as the success type for os::GetDiskUsage.
*/
struct DiskSpace {
u64 used_bytes; ///< Currently used disk space in bytes.
u64 total_bytes; ///< Total disk space in bytes.
u64 usedBytes; ///< Currently used disk space in bytes.
u64 totalBytes; ///< Total disk space in bytes.
};
/**