Compare commits

...

17 Commits
dev ... master

Author SHA1 Message Date
1c7f043e6f Fixed compilation using GCC due to duplicate template parameter name. 2025-09-22 21:47:05 +02:00
Patrick Wuttke
9ae424e968 Merge branch 'master' of https://git.mewin.de/mewin/mijin2 2025-09-22 21:42:51 +02:00
Patrick Wuttke
5a111df9ea Added void variant of Result. 2025-09-22 21:42:43 +02:00
Patrick Wuttke
32ccaad00a Added Stream::copyTo() and fixed read flag in FileStream::getFeatures().
Made throwOnError() throw Exception instead of std::runtime_error.
2025-09-22 21:41:55 +02:00
Patrick Wuttke
10d9b4c98f Made VectorMap find and access functions templates so you don't have to construct the key. 2025-09-22 21:40:53 +02:00
Patrick Wuttke
cc20702249 Added [[maybe_unused]] to std::atexit() call to fix non-debug builds. 2025-09-22 21:39:46 +02:00
Patrick Wuttke
7d6fcc60fc Added support for forwarding exceptions via Future. 2025-09-22 21:39:03 +02:00
3891c0f8ce Added option to quoted() function to replace newlines. 2025-09-21 12:36:41 +02:00
7da2f7b7f4 Added note about getKnownFolder() to getHomeFolder() deprecation hint. 2025-09-21 12:35:46 +02:00
bd06118b29 Normalize paths created by the RelativeFileSystemAdapter. Fixes issues with trailin "/.". 2025-09-21 12:34:38 +02:00
Patrick Wuttke
4d19752964 Added quoted() string helper. 2025-08-30 00:31:27 +02:00
Patrick Wuttke
0e988a4d9e Added member_pointer_of traits. 2025-08-30 00:31:05 +02:00
Patrick Wuttke
a95885880f Added formatter for exceptions. 2025-08-30 00:30:47 +02:00
Patrick Wuttke
d76e64c062 Fixed converting auf DynamicPointers, added wrapDynamic helper to simplify creating non-owning DynamicPointers. 2025-08-30 00:30:09 +02:00
Patrick Wuttke
e704c082b7 Some fixes for logging, added MIJIN_LOG_IF and the DebugOutputLogSink. 2025-08-30 00:29:16 +02:00
Patrick Wuttke
b44d6feb97 Added MIJIN_RTTI macro for detecting if RTTI is available. 2025-08-30 00:28:19 +02:00
Patrick Wuttke
e91184ec82 Fixed current loop not being reset on exceptions. 2025-08-30 00:25:16 +02:00
18 changed files with 512 additions and 70 deletions

View File

@ -5,10 +5,6 @@
#define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
# define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 // Capture stack each time a coroutine is started. Warning, expensive! // TODO: maybe implement a lighter version only storing the return address?
#endif
#include <any>
#include <chrono>
#include <coroutine>
@ -25,7 +21,17 @@
#include "../util/flag.hpp"
#include "../util/iterators.hpp"
#include "../util/misc.hpp"
#include "../util/scope_guard.hpp"
#include "../util/traits.hpp"
#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO)
# define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 // Capture stack each time a coroutine is started. Warning, expensive! // TODO: maybe implement a lighter version only storing the return address?
#endif
#if !defined(MIJIN_COROUTINE_ENABLE_EXCEPTIONS)
# define MIJIN_COROUTINE_ENABLE_EXCEPTIONS 0
#endif
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
#include "../debug/stacktrace.hpp"
#endif
@ -185,10 +191,16 @@ struct TaskReturn<void, TPromise>
};
}
template<typename TValue>
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
using TaskFuture = Future<TValue, TAllocator, MIJIN_COROUTINE_ENABLE_EXCEPTIONS>;
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
using TaskFuturePtr = FuturePtr<TValue, TAllocator, MIJIN_COROUTINE_ENABLE_EXCEPTIONS>;
template<typename TValue, template<typename> typename TAllocator>
struct TaskAwaitableFuture
{
FuturePtr<TValue> future;
TaskFuturePtr<TValue, TAllocator> future;
[[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return future->ready(); }
constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {}
@ -328,12 +340,12 @@ struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TT
// constexpr void unhandled_exception() MIJIN_NOEXCEPT {}
template<typename TValue>
auto await_transform(FuturePtr<TValue> future, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT
template<typename TValue, template<typename> typename TAllocator2>
auto await_transform(TaskFuturePtr<TValue, TAllocator2> future, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!");
sharedState_->sourceLoc = std::move(sourceLoc);
TaskAwaitableFuture<TValue> awaitable{future};
TaskAwaitableFuture<TValue, TAllocator> awaitable{future};
if (!awaitable.await_ready())
{
state_.status = TaskStatus::WAITING;
@ -634,10 +646,10 @@ public:
void setUncaughtExceptionHandler(exception_handler_t handler) MIJIN_NOEXCEPT { uncaughtExceptionHandler_ = std::move(handler); }
template<typename TResult>
FuturePtr<TResult> addTaskImpl(TaskBase<TResult, TAllocator> task, TaskHandle* outHandle) MIJIN_NOEXCEPT;
TaskFuturePtr<TResult, TAllocator> addTaskImpl(TaskBase<TResult, TAllocator> task, TaskHandle* outHandle) MIJIN_NOEXCEPT;
template<typename TResult>
FuturePtr<TResult> addTask(TaskBase<TResult, TAllocator> task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT
TaskFuturePtr<TResult, TAllocator> addTask(TaskBase<TResult, TAllocator> task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT
{
static_assert(TaskAllocatorTraits<TAllocator>::default_is_valid_v, "Allocator is not valid when default constructed, use makeTask() instead.");
return addTaskImpl(std::move(task), outHandle);
@ -663,7 +675,7 @@ protected:
protected:
static inline TaskLoop*& currentLoopStorage() MIJIN_NOEXCEPT;
template<typename TResult>
static inline void setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT;
static inline void setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT_IF(!MIJIN_COROUTINE_ENABLE_EXCEPTIONS);
};
template<typename TResult = void>
@ -810,12 +822,12 @@ TaskBase<TResult, TAllocator>::~TaskBase() MIJIN_NOEXCEPT
template<template<typename> typename TAllocator>
template<typename TResult>
FuturePtr<TResult> TaskLoop<TAllocator>::addTaskImpl(TaskBase<TResult, TAllocator> task, TaskHandle* outHandle) MIJIN_NOEXCEPT
TaskFuturePtr<TResult, TAllocator> TaskLoop<TAllocator>::addTaskImpl(TaskBase<TResult, TAllocator> task, TaskHandle* outHandle) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!");
task.setLoop(this);
FuturePtr<TResult> future = std::allocate_shared<Future<TResult>>(TAllocator<Future<TResult>>(allocator_), allocator_);
TaskFuturePtr<TResult, TAllocator> future = std::allocate_shared<TaskFuture<TResult, TAllocator>>(TAllocator<Future<TResult, TAllocator>>(allocator_), allocator_);
auto setFuture = &setFutureHelper<TResult>;
if (outHandle != nullptr)
@ -854,7 +866,7 @@ TaskStatus TaskLoop<TAllocator>::tickTask(StoredTask& task)
while (status == TaskStatus::RUNNING);
impl::gCurrentTaskState = nullptr;
#if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING
#if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING && !MIJIN_COROUTINE_ENABLE_EXCEPTIONS
if (task.task && task.task->exception())
{
try
@ -882,7 +894,22 @@ TaskStatus TaskLoop<TAllocator>::tickTask(StoredTask& task)
#endif // MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING
if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED)
{
task.setFuture(task);
try
{
task.setFuture(task);
}
catch(TaskCancelled&) {}
catch(...)
{
if (uncaughtExceptionHandler_)
{
uncaughtExceptionHandler_(std::current_exception());
}
else
{
throw;
}
}
}
return status;
}
@ -909,10 +936,22 @@ template<template<typename> typename TAllocator>
template<template<typename> typename TAllocator>
template<typename TResult>
/* static */ inline void TaskLoop<TAllocator>::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT
/* static */ inline void TaskLoop<TAllocator>::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT_IF(!MIJIN_COROUTINE_ENABLE_EXCEPTIONS)
{
TaskBase<TResult, TAllocator>& task = *static_cast<TaskBase<TResult, TAllocator>*>(storedTask.task->raw());
auto future = std::any_cast<FuturePtr<TResult>>(storedTask.resultData);
const auto& future = std::any_cast<TaskFuturePtr<TResult, TAllocator>&>(storedTask.resultData);
#if MIJIN_COROUTINE_ENABLE_EXCEPTIONS
if (task.state().exception)
{
if (future.use_count() < 2)
{
// future has been discarded, but someone must handle the exception
std::rethrow_exception(task.state().exception);
}
future->setException(task.state().exception);
return;
}
#endif
if constexpr (!std::is_same_v<TResult, void>)
{
@ -997,6 +1036,9 @@ inline auto BaseSimpleTaskLoop<TAllocator>::tick() -> CanContinue
// set current taskloop
MIJIN_ASSERT(TaskLoop<TAllocator>::currentLoopStorage() == nullptr, "Trying to tick a loop from a coroutine, this is not supported.");
TaskLoop<TAllocator>::currentLoopStorage() = this;
MIJIN_SCOPE_EXIT {
TaskLoop<TAllocator>::currentLoopStorage() = nullptr;
};
threadId_ = std::this_thread::get_id();
// move over all tasks from newTasks
@ -1046,8 +1088,6 @@ inline auto BaseSimpleTaskLoop<TAllocator>::tick() -> CanContinue
canContinue = CanContinue::YES;
}
}
// reset current loop
TaskLoop<TAllocator>::currentLoopStorage() = nullptr;
// remove any tasks that have been transferred to another queue
it = std::remove_if(tasks_.begin(), tasks_.end(), [](const StoredTask& task) {
@ -1247,14 +1287,14 @@ inline TaskAwaitableSuspend c_suspend() {
return TaskAwaitableSuspend();
}
template<template<typename...> typename TCollection, typename TType, typename... TTemplateArgs>
Task<> c_allDone(const TCollection<FuturePtr<TType>, TTemplateArgs...>& futures)
template<template<typename...> typename TCollection, FutureType TFuture, typename... TTemplateArgs>
Task<> c_allDone(const TCollection<TFuture, TTemplateArgs...>& futures)
{
bool allDone = true;
do
{
allDone = true;
for (const FuturePtr<TType>& future : futures)
for (const TFuture& future : futures)
{
if (future && !future->ready()) {
allDone = false;

View File

@ -4,6 +4,7 @@
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
#include <exception>
#include <memory>
#include <optional>
#include <tuple>
@ -27,43 +28,113 @@ namespace mijin
//
// public types
//
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false>
class Future;
// TODO: add support for mutexes and waiting for futures
namespace impl
{
template<typename TValue>
template<typename TValue, bool exceptions>
struct FutureStorage
{
Optional<TValue> value;
[[nodiscard]]
bool hasValue() const MIJIN_NOEXCEPT
{
return !value.empty();
}
void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
[[nodiscard]] TValue& getValue() MIJIN_NOEXCEPT { return value.get(); }
};
// template<typename TValue>
// struct FutureStorage<TValue&>
// {
// Optional<TValue*> value;
//
// void setValue(TValue& value_) MIJIN_NOEXCEPT { value = &value_; }
// [[nodiscard]] TValue& getValue() const MIJIN_NOEXCEPT { return *value.get(); }
// };
template<>
struct FutureStorage<void, false>
{
bool isSet = false;
[[nodiscard]]
bool hasValue() const MIJIN_NOEXCEPT
{
return isSet;
}
void setValue() MIJIN_NOEXCEPT
{
isSet = true;
}
void getValue() MIJIN_NOEXCEPT {}
};
#if MIJIN_ENABLE_EXCEPTIONS
template<typename TValue>
struct FutureStorage<TValue, true>
{
Optional<TValue> value;
std::exception_ptr exception;
[[nodiscard]]
bool hasValue() const MIJIN_NOEXCEPT
{
if (exception) {
return true;
}
return !value.empty();
}
void setException(std::exception_ptr exc) MIJIN_NOEXCEPT
{
exception = std::move(exc);
}
void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
[[nodiscard]] TValue& getValue()
{
if (exception) {
std::rethrow_exception(exception);
}
return value.get();
}
};
template<>
struct FutureStorage<void>
struct FutureStorage<void, true>
{
bool isSet = false;
std::exception_ptr exception;
[[nodiscard]]
bool hasValue() const MIJIN_NOEXCEPT
{
if (exception) {
return true;
}
return isSet;
}
void setException(std::exception_ptr exc) MIJIN_NOEXCEPT
{
exception = std::move(exc);
}
void setValue() MIJIN_NOEXCEPT
{
isSet = true;
}
void getValue()
{
if (exception) {
std::rethrow_exception(exception);
}
}
};
#endif
} // namespace impl
template<typename TValue, template<typename> typename TAllocator>
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
class Future
{
private:
[[no_unique_address]] impl::FutureStorage<TValue> value_;
bool isSet_ = false;
impl::FutureStorage<TValue, exceptions> value_;
public:
Future() = default;
Future(const Future&) = delete;
@ -80,10 +151,12 @@ public:
constexpr bool operator!() const MIJIN_NOEXCEPT { return !ready(); }
public: // access
[[nodiscard]]
constexpr decltype(auto) get() MIJIN_NOEXCEPT
constexpr decltype(auto) get() MIJIN_NOEXCEPT_IF(!exceptions)
{
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) {
MIJIN_ASSERT(ready(), "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>)
{
value_.getValue(); // in case of exceptions
return;
}
else {
@ -91,10 +164,12 @@ public: // access
}
}
[[nodiscard]]
constexpr decltype(auto) get() const MIJIN_NOEXCEPT
constexpr decltype(auto) get() const MIJIN_NOEXCEPT_IF(!exceptions)
{
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) {
MIJIN_ASSERT(ready(), "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>)
{
value_.getValue(); // in case of exceptions
return;
}
else {
@ -104,22 +179,22 @@ public: // access
[[nodiscard]]
constexpr bool ready() const MIJIN_NOEXCEPT
{
return isSet_;
return value_.hasValue();
}
public: // modification
template<typename TArg> requires (!std::is_same_v<TValue, void>)
constexpr void set(TArg&& value) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
value_.setValue(std::move(value));
isSet_ = true;
sigSet.emit();
}
constexpr void set() MIJIN_NOEXCEPT requires (std::is_same_v<TValue, void>)
{
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
isSet_ = true;
if constexpr (std::is_same_v<TValue, void>) {
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
if constexpr (std::is_same_v<TValue, void>)
{
value_.setValue();
sigSet.emit();
}
else {
@ -127,12 +202,38 @@ public: // modification
MIJIN_ERROR("Attempting to call set(void) on future with value.");
}
}
constexpr void setException(std::exception_ptr exception) requires (exceptions)
{
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
if constexpr (exceptions)
{
value_.setException(std::move(exception));
}
}
public: // signals
BaseSignal<TAllocator> sigSet;
};
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false>
using FuturePtr = std::shared_ptr<Future<TValue, TAllocator, exceptions>>;
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
using FuturePtr = std::shared_ptr<Future<TValue, TAllocator>>;
using ExceptFuture = Future<TValue, TAllocator, true>;
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
using ExceptFuturePtr = std::shared_ptr<Future<TValue, TAllocator, true>>;
template<typename T>
struct is_future : std::false_type {};
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
struct is_future<Future<TValue, TAllocator, exceptions>> : std::true_type {};
template<typename T>
inline constexpr bool is_future_t = is_future<T>::value;
template<typename T>
concept FutureType = is_future<T>::value;
//
// public functions

View File

@ -133,7 +133,8 @@ public:
VectorMap& operator=(VectorMap&&) = default;
auto operator<=>(const VectorMap& other) const noexcept = default;
TValue& operator[](const TKey& key)
template<typename TIndex>
TValue& operator[](const TIndex& key)
{
auto it = find(key);
if (it != end())
@ -143,7 +144,8 @@ public:
return emplace(key, TValue()).first->second;
}
const TValue& operator[](const TKey& key) const
template<typename TIndex>
const TValue& operator[](const TIndex& key) const
{
return at(key);
}
@ -251,8 +253,9 @@ public:
return eraseImpl(idx, count);
}
template<typename TSearch>
[[nodiscard]]
iterator find(const TKey& key) MIJIN_NOEXCEPT
iterator find(const TSearch& key) MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
@ -264,8 +267,9 @@ public:
return end();
}
template<typename TSearch>
[[nodiscard]]
const_iterator find(const TKey& key) const MIJIN_NOEXCEPT
const_iterator find(const TSearch& key) const MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{

View File

@ -122,7 +122,7 @@ bool initDbgHelp() MIJIN_NOEXCEPT
return false;
}
const int result = std::atexit(&cleanupDbgHelp);
[[maybe_unused]] const int result = std::atexit(&cleanupDbgHelp);
MIJIN_ASSERT(result == 0, "Error registering DbgHelp cleanup handler.");
// only copy in the end so we can still figure out if initialization was successful

View File

@ -61,6 +61,26 @@ namespace mijin
#endif
#endif
#if !defined(MIJIN_RTTI)
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC
#if defined(__GXX_RTTI)
#define MIJIN_RTTI 1
#else
#define MIJIN_RTTI 0
#endif
#elif MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#define MIJIN_RTTI (__has_feature(cxx_rtti))
#elif MIJIN_COMPILER == MIJIN_COMPILER_MSVC
#if defined(_CPPRTTI)
#define MIJIN_RTTI 1
#else
#define MIJIN_RTTI 0
#endif
#else
#define MIJIN_RTTI 0
#endif
#endif
//
// public constants
//

View File

@ -290,6 +290,29 @@ mijin::Task<StreamError> Stream::c_readLine(std::string& outString)
co_return StreamError::SUCCESS;
}
StreamError Stream::copyTo(Stream& other)
{
MIJIN_ASSERT(getFeatures().read, "Stream must support reading.");
MIJIN_ASSERT(other.getFeatures().write, "Other stream must support writing.");
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (const StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
return error;
}
if (const StreamError error = other.writeRaw(chunk.data(), bytesRead); error != StreamError::SUCCESS)
{
return error;
}
}
return StreamError::SUCCESS;
}
FileStream::~FileStream()
{
if (handle) {
@ -441,7 +464,7 @@ StreamFeatures FileStream::getFeatures()
if (handle)
{
return {
.read = (mode == FileOpenMode::READ),
.read = (mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE),
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
.tell = true,
.seek = true,

View File

@ -275,6 +275,8 @@ public:
StreamError getTotalLength(std::size_t& outLength);
StreamError copyTo(Stream& otherStream);
template<template<typename> typename TAllocator>
StreamError readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
@ -544,7 +546,7 @@ inline void throwOnError(mijin::StreamError error)
if (error == mijin::StreamError::SUCCESS) {
return;
}
throw std::runtime_error(errorName(error));
throw Exception(errorName(error));
}
inline void throwOnError(mijin::StreamError error, std::string message)
@ -552,7 +554,7 @@ inline void throwOnError(mijin::StreamError error, std::string message)
if (error == mijin::StreamError::SUCCESS) {
return;
}
throw std::runtime_error(message + ": " + errorName(error));
throw Exception(message + ": " + errorName(error));
}
template<typename TSuccess>

View File

@ -0,0 +1,67 @@
#pragma once
#if !defined(MIJIN_LOGGING_DEBUG_OUTPUT_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_DEBUG_OUTPUT_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../detect.hpp"
#include "../util/traits.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#pragma comment(lib, "Kernel32.lib")
extern "C" void OutputDebugStringA(const char* lpOutputString);
extern "C" void OutputDebugStringW(const wchar_t* lpOutputString);
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseDebugOutputSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
{
public:
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
using typename base_t::char_t;
using typename base_t::allocator_t;
using typename base_t::formatter_ptr_t;
using typename base_t::message_t;
public:
explicit BaseDebugOutputSink(formatter_ptr_t formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)) {}
void handleMessageFormatted(const message_t&, const char_t* formatted) MIJIN_NOEXCEPT override
{
if constexpr (std::is_same_v<char_t, char>)
{
OutputDebugStringA(formatted);
OutputDebugStringA("\n");
}
else if constexpr (std::is_same_v<char_t, wchar_t>)
{
OutputDebugStringW(formatted);
OutputDebugStringW(L"\n");
}
else if constexpr (sizeof(char_t) == sizeof(char))
{
// char8_t etc.
OutputDebugStringA(std::bit_cast<const char*>(formatted));
OutputDebugStringA("\n");
}
else
{
static_assert(always_false_v<char_t>, "Character type not supported.");
}
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(DebugOutputSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#endif // !defined(MIJIN_LOGGING_DEBUG_OUTPUT_SINK_HPP_INCLUDED)

View File

@ -92,10 +92,10 @@ public:
using string_t = formatter_t::string_t;
using typename base_t::message_t;
private:
not_null_t<formatter_ptr_t> mFormatter;
formatter_ptr_t mFormatter;
string_t mBuffer;
public:
explicit BaseFormattingLogSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
explicit BaseFormattingLogSink(formatter_ptr_t formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: mFormatter(std::move(formatter)), mBuffer(std::move(allocator))
{}

View File

@ -254,10 +254,14 @@ MIJIN_DEFINE_LOG_LEVEL(ERROR, MIJIN_LOG_LEVEL_VALUE_ERROR)
#define MIJIN_LOG_ALWAYS(level, channel, ...) MIJIN_FUNCNAME_GET_LOGGER().log( \
MIJIN_LOG_LEVEL_OBJECT(level), MIJIN_LOG_CHANNEL_OBJECT(channel), std::source_location::current(), __VA_ARGS__ \
)
#define MIJIN_LOG(level, channel, ...) \
#define MIJIN_LOG(level, channel, ...) \
if constexpr (MIJIN_LOG_LEVEL_OBJECT(level).value < MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE()) {} \
else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__)
#define MIJIN_LOG_IF(cond, level, channel, ...) \
if constexpr (MIJIN_LOG_LEVEL_OBJECT(level).value < MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE()) {} \
else if (cond) MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__)
#define MIJIN_SET_CLASS_LOGGER(loggerExpr) \
const auto& MIJIN_FUNCNAME_GET_LOGGER() const noexcept \
{ \
@ -268,6 +272,11 @@ auto MIJIN_FUNCNAME_GET_LOGGER = [&]() -> const auto& \
{ \
return loggerExpr; \
};
#define MIJIN_SET_NS_LOGGER(loggerExpr) \
inline const mijin::Logger& MIJIN_FUNCNAME_GET_LOGGER() \
{ \
return loggerExpr; \
}
#define MIJIN_SET_CLASS_MIN_LOG_LEVEL_COMPILE(level) \
int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT \
{ \

View File

@ -22,7 +22,7 @@ public:
private:
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
explicit BaseStdioSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
explicit BaseStdioSink(formatter_ptr_t formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)) {}

View File

@ -35,10 +35,11 @@ public:
{
MIJIN_ASSERT((std::bit_cast<std::uintptr_t>(ptr) & 1) == 0, "Invalid address, DynamicPointer requires addresses to be divisible by two.");
}
template<typename TOther, typename TOtherDeleter> requires (std::is_constructible_v<TDeleter, TOtherDeleter&&>)
template<typename TOther, typename TOtherDeleter> requires (std::is_assignable_v<T*&, TOther*> && std::is_constructible_v<TDeleter, TOtherDeleter&&>)
constexpr DynamicPointer(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_convertible_v<TOtherDeleter, TDeleter>))
: mData(std::exchange(other.mData, 0)), mDeleter(std::move(other.mDeleter)) {
MIJIN_ASSERT(other.mData == 0, "");
: DynamicPointer(other.get(), other.isOwning() ? Owning::YES : Owning::NO, TDeleter(std::move(other.mDeleter)))
{
other.mData = 0;
}
constexpr ~DynamicPointer() noexcept
{
@ -64,6 +65,15 @@ public:
return *this;
}
// template<typename TOther, typename TOtherDeleter> requires (std::is_base_of_v<TOther, T>)
// constexpr operator DynamicPointer<TOther, TOtherDeleter>() && // MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TDeleter>>)
// {
// const Owning owning = isOwning() ? Owning::YES : Owning::NO;
// T* const ptr = release();
//
// return DynamicPointer<TOther, TOtherDeleter>(static_cast<TOther*>(ptr), owning, TOtherDeleter(std::move(mDeleter)));
// }
template<typename TOther, typename TOtherDeleter> requires(std::equality_comparable_with<T, TOther>)
auto operator<=>(const DynamicPointer<TOther, TOtherDeleter>& other) MIJIN_NOEXCEPT
{
@ -155,9 +165,9 @@ bool operator!=(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJI
}
template<typename T, typename... TArgs>
DynamicPointer<T, std::default_delete<T>> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
DynamicPointer<T> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
{
return DynamicPointer<T, std::default_delete<T>>(new T(std::forward<TArgs>(args)...), Owning::YES);
return DynamicPointer<T>(new T(std::forward<TArgs>(args)...), Owning::YES);
}
template<typename T, allocator_type_for<T> TAllocator, typename... TArgs>
@ -171,6 +181,12 @@ DynamicPointer<T, AllocatorDeleter<TAllocator>> makeDynamicWithAllocator(TAlloca
}
return DynamicPointer<T, AllocatorDeleter<TAllocator>>(obj, Owning::YES, AllocatorDeleter<TAllocator>(std::move(allocator)));
}
template<typename T>
DynamicPointer<T> wrapDynamic(T* raw)
{
return DynamicPointer<T>(raw, Owning::NO);
}
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)

View File

@ -60,6 +60,32 @@ public:
[[nodiscard]] const TError& getError() const MIJIN_NOEXCEPT { return std::get<TError>(state_); }
};
namespace impl
{
struct ResultSuccess {};
}
template<typename TError>
class ResultBase<void, TError> : public ResultBase<impl::ResultSuccess, TError>
{
public:
ResultBase() MIJIN_NOEXCEPT : ResultBase<impl::ResultSuccess, TError>(impl::ResultSuccess{}) {}
ResultBase(const ResultBase&) MIJIN_NOEXCEPT = default;
ResultBase(ResultBase&&) MIJIN_NOEXCEPT = default;
ResultBase(TError errorValue) MIJIN_NOEXCEPT : ResultBase<impl::ResultSuccess, TError>(std::move(errorValue)) {}
ResultBase& operator=(const ResultBase&) = default;
ResultBase& operator=(ResultBase&&) = default;
impl::ResultSuccess& operator*() MIJIN_NOEXCEPT = delete;
const impl::ResultSuccess& operator*() const MIJIN_NOEXCEPT = delete;
impl::ResultSuccess* operator->() MIJIN_NOEXCEPT = delete;
const impl::ResultSuccess* operator->() const MIJIN_NOEXCEPT = delete;
[[nodiscard]] impl::ResultSuccess& getValue() MIJIN_NOEXCEPT = delete;
[[nodiscard]] const impl::ResultSuccess& getValue() const MIJIN_NOEXCEPT = delete;
};
struct ResultError
{
std::string message;
@ -73,7 +99,7 @@ struct ResultError
ResultError& operator=(ResultError&&) MIJIN_NOEXCEPT = default;
};
template<typename TSuccess>
template<typename TSuccess = void>
using Result = ResultBase<TSuccess, ResultError>;
//

View File

@ -4,8 +4,11 @@
#ifndef MIJIN_UTIL_EXCEPTION_HPP_INCLUDED
#define MIJIN_UTIL_EXCEPTION_HPP_INCLUDED 1
#include <format>
#include <stdexcept>
#include <typeinfo>
#include "../detect.hpp"
#include "../debug/stacktrace.hpp"
namespace mijin
@ -73,5 +76,76 @@ void walkExceptionCause(const std::exception_ptr& cause, TFunc func)
}
}
}
template<typename TFunc>
void walkException(const std::exception& exc, TFunc func)
{
func(exc);
}
template<typename TFunc>
void walkException(const mijin::Exception& exc, TFunc func)
{
func(exc);
walkExceptionCause(exc.getCause(), func);
}
} // namespace mijin
template<typename TChar>
struct std::formatter<mijin::Exception, TChar>
{
using char_t = TChar;
template<class TContext>
constexpr TContext::iterator parse(TContext& ctx)
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != MIJIN_SMART_QUOTE(char_t, '}'))
{
throw std::format_error("invalid format");
}
return it;
}
template<typename TContext>
TContext::iterator format(const mijin::Exception& exception, TContext& ctx) const
{
using namespace std::literals;
auto it = ctx.out();
bool first = true;
mijin::walkException(exception, [&]<typename T>(const T& exc)
{
if constexpr (!std::is_same_v<T, std::nullptr_t>)
{
if (!first) {
it = std::ranges::copy(MIJIN_SMART_QUOTE(char_t, "\nCaused by:\n"sv), it).out;
}
first = false;
#if MIJIN_RTTI
it = std::ranges::copy(std::basic_string_view(typeid(exc).name()), it).out;
it = std::ranges::copy(MIJIN_SMART_QUOTE(char_t, ": "sv), it).out;
#endif
it = std::ranges::copy(std::basic_string_view(exc.what()), it).out;
if constexpr (std::is_same_v<T, mijin::Exception>)
{
if (const mijin::Result<mijin::Stacktrace>& trace = exc.getStacktrace(); trace.isSuccess())
{
*it = MIJIN_SMART_QUOTE(char_t, '\n');
++it;
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "{}"), trace.getValue());
}
}
}
else
{
it = std::ranges::copy(MIJIN_SMART_QUOTE(char_t, "<unknown exception>"sv), it).out;
}
});
return it;
}
};
#endif // MIJIN_UTIL_EXCEPTION_HPP_INCLUDED

View File

@ -1020,6 +1020,54 @@ bool convertStringType(const TFrom* strFrom, std::basic_string<TTo, TToTraits, T
{
return convertStringType(std::basic_string_view<TFrom>(strFrom), outString);
}
struct StringQuoteOptions
{
bool replaceNewlines = false;
};
template<StringQuoteOptions options = {}, typename TChar, typename TTraits, typename TAlloc = MIJIN_DEFAULT_ALLOCATOR<TChar>>
std::basic_string<TChar, TTraits, TAlloc> quoted(std::basic_string_view<TChar, TTraits> input)
{
std::basic_string<TChar, TTraits> result;
result.reserve(input.size() + 2);
result.push_back(TChar('"'));
for (const TChar chr : input)
{
switch (chr)
{
case TChar('"'):
case TChar('\\'):
result.push_back(TChar('\\'));
break;
case TChar('\n'):
if constexpr (options.replaceNewlines)
{
result.push_back(TChar('\\'));
result.push_back(TChar('n'));
continue;
}
break;
case TChar('\r'):
if constexpr (options.replaceNewlines)
{
result.push_back(TChar('\\'));
result.push_back(TChar('r'));
continue;
}
break;
}
result.push_back(chr);
}
result.push_back(TChar('"'));
return result;
}
template<StringQuoteOptions options = {}, typename TChar, typename TTraits, typename TAlloc>
std::basic_string<TChar, TTraits, TAlloc> quoted(const std::basic_string<TChar, TTraits, TAlloc>& input)
{
return quoted<options, TChar, TTraits, TAlloc>(std::basic_string_view(input));
}
} // namespace mijin
#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)

View File

@ -192,6 +192,18 @@ struct is_type_member<TElement, TCollection<Ts...>>
template<typename TElement, typename TCollection>
constexpr bool is_type_member_v = is_type_member<TElement, TCollection>::value;
template<typename T, typename TObject>
struct is_member_object_pointer_of : std::false_type {};
template<typename TMember, typename TObject>
struct is_member_object_pointer_of<TMember (TObject::*), TObject> : std::true_type {};
template<typename T, typename TObject>
inline constexpr bool is_member_object_pointer_of_v = is_member_object_pointer_of<T, TObject>::value;
template<typename T, typename TObject>
concept member_object_pointer_of = is_member_object_pointer_of_v<T, TObject>;
template<typename TFrom, typename TTo>
using copy_const_t = std::conditional_t<std::is_const_v<TFrom>, std::add_const_t<TTo>, std::remove_const_t<TTo>>;

View File

@ -80,7 +80,7 @@ class FileSystemAdapter
public:
virtual ~FileSystemAdapter() = default;
[[deprecated("Will be removed ASAP")]]
[[deprecated("Will be removed ASAP, use getKnownFolder(KnownFolder::USER_HOME) from platform/folders.hpp instead.")]]
[[nodiscard]] virtual fs::path getHomeFolder() { return {}; } // TODO: get rid of this ...
[[nodiscard]] virtual std::vector<FileInfo> listFiles(const fs::path& folder) = 0;
[[nodiscard]] virtual FileInfo getFileInfo(const fs::path& file) = 0;

View File

@ -97,7 +97,7 @@ fs::path RelativeFileSystemAdapter<TWrapped>::appendPath(const fs::path& other)
else {
combinedPath /= other;
}
return combinedPath;
return combinedPath.lexically_normal();
}
namespace vfs_pipe