Compare commits

...

68 Commits

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
Patrick Wuttke
51092bb4cb Added splitView() and splitLines() to string utility functions. 2025-07-15 18:21:10 +02:00
Patrick Wuttke
02e99bbc82 Made logger thread-safe and added filters. 2025-07-14 17:16:24 +02:00
Patrick Wuttke
ad627b7c70 Added NotNullable::release() function. 2025-07-14 17:16:09 +02:00
Patrick Wuttke
4a9a60c7f5 Replaced default deleter used in DynamicPointer so it fits better with other functions. 2025-07-14 17:15:25 +02:00
Patrick Wuttke
d8b03893b3 Merge branch 'master' of https://git.mewin.de/mewin/mijin2 2025-07-14 10:08:04 +02:00
Patrick Wuttke
7939d458b3 Added Stream functions for reading and writing 0-terminated strings. 2025-07-14 10:07:57 +02:00
Patrick Wuttke
b5546067a8 Added missing include. 2025-07-14 10:07:37 +02:00
9e572061da Added DEBUG to macros being undefed. 2025-07-11 14:11:14 +02:00
d0be5f7739 Fixed use-after-move in TaskLoop::addTaskImpl() and some minor compilation problems. 2025-07-10 02:16:29 +02:00
e6556c6f90 Fixed convertCharType when converting from wchar_t to char. 2025-07-09 00:57:45 +02:00
addae96c91 Made Stream::readAsString() and Stream::c_readAsString() work with arbitrary std::basic_string instances. 2025-07-09 00:57:19 +02:00
cf860392a5 Added missing copy assignment operator for ConstMemoryView. 2025-07-08 00:35:12 +02:00
8ad34427f3 Made TypelessBuffer allocator-aware and split some of its functionality into MemoryView type. 2025-07-07 00:12:34 +02:00
59780ed6de Fixed compilation of MemoryFileSystem if MIJIN_DEFAULT_ALLOCATOR is not copy-constructible. 2025-07-06 12:37:45 +02:00
888b0a16f7 Fixed use of MIJIN_CONFIG_HEADER. 2025-07-06 12:37:06 +02:00
d29a025ec5 Properly constexpred and noexcepted BoxedObject. 2025-07-03 09:10:36 +02:00
40c0d888e6 Added std::hash for tuples, because why not. 2025-07-02 23:10:30 +02:00
bf53622b19 Added missing construction and destruction of StackAllocatorSnapshotData, which is only required when MIJIN_STACK_ALLOCATOR_DEBUG is 2. 2025-07-02 16:40:46 +02:00
6090d5fc74 Implemented stacktrace collection for Windows. 2025-07-02 16:39:26 +02:00
a1d7a63aba Added flusing to the stdio log sink. 2025-07-02 16:39:02 +02:00
1d32530be7 Added allReady() and getAll() functions for futures and c_allDone() with tasks for tasks. 2025-07-01 18:19:38 +02:00
973b62a348 Added some missing headers. 2025-06-28 02:23:23 +02:00
86f3790ce1 Added stack allocator snapshots. 2025-06-26 16:55:33 +02:00
1cbd435fbf Added missing Signal constructor taking const-qualified member functions. 2025-06-25 00:45:40 +02:00
573c431dbd Lots of windows fixes and some more improvements. 2025-06-24 15:21:01 +02:00
36a908ab8a Added StackAllocator type. 2025-06-23 00:24:03 +02:00
a956560183 Made coroutines allocator-aware (let's hope this really works). 2025-06-23 00:23:50 +02:00
2fc03e4050 Made Signal allocator-aware. 2025-06-23 00:21:46 +02:00
94e94f02b6 Modified assertion macros so they evaluate to an empty instruction instead of nothing in non-debug builds. 2025-06-23 00:21:24 +02:00
7284d3d15f Added STL formatters for Stacktrace and Stackframe. 2025-06-23 00:20:17 +02:00
2b368c1d4f Added converting constructor for AllocatorDeleter. 2025-06-23 00:19:30 +02:00
9c4765dbaf Added converting constructors for NotNullable. 2025-06-23 00:18:47 +02:00
05bc3d5147 Added optional_base trait. 2025-06-23 00:17:30 +02:00
232a01eb28 Added alignUp() variant for pointers. 2025-06-23 00:16:52 +02:00
c9c4eff130 Added (very limited) support for ansi coloring to log formatter. 2025-06-21 17:01:37 +02:00
8a9df15dd0 CLang advertises C++-26 features but warns us when we use them :/. 2025-06-21 15:07:12 +02:00
465a97ded5 Removed logger.cpp again. 2025-06-21 15:06:39 +02:00
b91eb34789 Massively overengineered logger to support different character types. 2025-06-21 14:16:19 +02:00
061c58ef41 Added more stuff like Logging, DynamicPointer, utilities for using STL allocators and NotNullable. 2025-06-21 08:07:57 +02:00
17bd408d3c Fixed test and CLang compilation. 2025-05-20 21:15:51 +02:00
45623e5273 Fixed GCC compilation. 2025-05-17 11:14:03 +02:00
48fd006819 Added MemoryFileSystemAdapter. 2025-03-28 11:44:28 +01:00
d7f968db3a Deprecated FileSystemAdapter::getHomeFolder(). 2025-03-28 11:44:15 +01:00
e35f5a35f8 Some improvements to VectorMap interface. Added moving operator[], contains and [[nodiscard]] to find(). 2025-03-28 11:43:32 +01:00
Patrick Wuttke
91d53805b5 Semi-disabled bitFlagsToInt() again, since it is quite error-prone. 2025-03-27 15:07:45 +01:00
Patrick Wuttke
8bad5e4346 Fixed stars- and endsWithIgnoreCase. 2025-03-27 15:07:17 +01:00
Patrick Wuttke
b007768790 Merge branch 'master' of https://git.mewin.de/mewin/mijin2 2025-03-27 12:18:57 +01:00
Patrick Wuttke
f6776d233d Added startsWithIgnoreCase() and endsWithIgnoreCase() functions. 2025-03-27 12:18:52 +01:00
719505ac05 Removed old LibConf file. 2025-03-18 17:19:38 +01:00
6dcd95b9f3 Added license. 2025-03-18 17:19:38 +01:00
Patrick Wuttke
a64bfde6af Added findIgnoreCase() utility function. 2025-03-18 15:07:55 +01:00
57 changed files with 4870 additions and 662 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Patrick Wuttke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

42
LibConf
View File

@ -1,42 +0,0 @@
Import('env')
mijin_sources = Split("""
source/mijin/async/coroutine.cpp
source/mijin/debug/stacktrace.cpp
source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp
source/mijin/io/stream.cpp
source/mijin/net/http.cpp
source/mijin/net/ip.cpp
source/mijin/net/socket.cpp
source/mijin/util/os.cpp
source/mijin/types/name.cpp
source/mijin/virtual_filesystem/filesystem.cpp
source/mijin/virtual_filesystem/stacked.cpp
""")
dependencies = []
if env['COMPILER_FAMILY'] in ('gcc', 'clang'):
lib_libbacktrace = env.Cook('libbacktrace')
dependencies.append(lib_libbacktrace)
cppdefines = []
if env['BUILD_TYPE'] == 'debug':
cppdefines += ['MIJIN_DEBUG=1', 'MIJIN_CHECKED_ITERATORS=1']
lib_mijin = env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/mijin',
source = mijin_sources,
dependencies = dependencies,
CPPDEFINES = list(env['CPPDEFINES']) + cppdefines
)
LIB_CONFIG = {
'CPPPATH': [env.Dir('source')],
'CPPDEFINES': cppdefines,
'DEPENDENCIES': [lib_mijin]
}
Return('LIB_CONFIG')

View File

@ -17,6 +17,7 @@ mijin_sources = Split("""
source/mijin/util/os.cpp
source/mijin/types/name.cpp
source/mijin/virtual_filesystem/filesystem.cpp
source/mijin/virtual_filesystem/memory.cpp
source/mijin/virtual_filesystem/stacked.cpp
""")

View File

@ -0,0 +1,50 @@
#pragma once
#if !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)
#define MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED 1
#include "./signal.hpp"
#include "../container/boxed_object.hpp"
namespace mijin
{
//
// public defines
//
//
// public constants
//
//
// public types
//
template<template<typename> typename TAllocator, typename... TArgs>
class BaseBoxedSignal : private BoxedObject<BaseSignal<TAllocator, TArgs...>>
{
private:
using base_t = BoxedObject<BaseSignal<TAllocator, TArgs...>>;
public:
using base_t::construct;
using base_t::destroy;
using base_t::moveTo;
MIJIN_BOXED_PROXY_FUNC(connect)
MIJIN_BOXED_PROXY_FUNC(disconnect)
MIJIN_BOXED_PROXY_FUNC(emit)
};
template<typename... TArgs>
using BoxedSignal = BaseBoxedSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
//
// public functions
//
} // namespace mijin
#endif // !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED)

View File

@ -26,205 +26,15 @@ namespace mijin
namespace impl
{
thread_local TaskLoop::StoredTask* gCurrentTask = nullptr;
thread_local std::shared_ptr<TaskSharedState> gCurrentTaskState;
}
//
// internal functions
//
void MultiThreadedTaskLoop::managerThread(std::stop_token stopToken) // NOLINT(performance-unnecessary-value-param)
{
setCurrentThreadName("Task Manager");
while (!stopToken.stop_requested())
{
// first clear out any parked tasks that are actually finished
auto itRem = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) {
return !task.task || task.task->status() == TaskStatus::FINISHED;
});
parkedTasks_.erase(itRem, parkedTasks_.end());
// then try to push any task from the buffer into the queue, if possible
for (auto it = parkedTasks_.begin(); it != parkedTasks_.end();)
{
if (!it->task->canResume())
{
++it;
continue;
}
if (readyTasks_.tryPushMaybeMove(*it)) {
it = parkedTasks_.erase(it);
}
else {
break;
}
}
// then clear the incoming task queue
while (true)
{
std::optional<StoredTask> task = queuedTasks_.tryPop();
if (!task.has_value()) {
break;
}
// try to directly move it into the next queue
if (readyTasks_.tryPushMaybeMove(*task)) {
continue;
}
// otherwise park it
parkedTasks_.push_back(std::move(*task));
}
// next collect tasks returning from the worker threads
while (true)
{
std::optional<StoredTask> task = returningTasks_.tryPop();
if (!task.has_value()) {
break;
}
if (task->task == nullptr || task->task->status() == TaskStatus::FINISHED) {
continue; // task has been transferred or finished
}
if (task->task->canResume() && readyTasks_.tryPushMaybeMove(*task)) {
continue; // instantly resume, no questions asked
}
// otherwise park it for future processing
parkedTasks_.push_back(std::move(*task));
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void MultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t workerId) // NOLINT(performance-unnecessary-value-param)
{
currentLoopStorage() = this; // forever (on this thread)
std::array<char, 16> threadName;
(void) std::snprintf(threadName.data(), 16, "Task Worker %lu", static_cast<unsigned long>(workerId));
setCurrentThreadName(threadName.data());
while (!stopToken.stop_requested())
{
// try to fetch a task to run
std::optional<StoredTask> task = readyTasks_.tryPop();
if (!task.has_value())
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
// run it
impl::gCurrentTask = &*task;
tickTask(*task);
impl::gCurrentTask = nullptr;
// and give it back
returningTasks_.push(std::move(*task));
}
}
//
// public functions
//
void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT
{
assertCorrectThread();
if (&otherLoop == this) {
return;
}
MIJIN_ASSERT_FATAL(currentTask_ != tasks_.end(), "Trying to call transferCurrentTask() while not running a task!");
// now start the transfer, first disown the task
StoredTask storedTask = std::move(*currentTask_);
currentTask_->task = nullptr; // just to be sure
// then send it over to the other loop
otherLoop.addStoredTask(std::move(storedTask));
}
void SimpleTaskLoop::addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT
{
storedTask.task->setLoop(this);
if (threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id())
{
// same thread, just copy it over
if (currentLoopStorage() != nullptr) {
// currently running, can't append to tasks_ directly
newTasks_.push_back(std::move(storedTask));
}
else {
tasks_.push_back(std::move(storedTask));
}
}
else
{
// other thread, better be safe
queuedTasks_.push(std::move(storedTask));
}
}
std::size_t SimpleTaskLoop::getActiveTasks() const MIJIN_NOEXCEPT
{
std::size_t sum = 0;
for (const StoredTask& task : mijin::chain(tasks_, newTasks_))
{
const TaskStatus status = task.task ? task.task->status() : TaskStatus::FINISHED;
if (status == TaskStatus::SUSPENDED || status == TaskStatus::RUNNING)
{
++sum;
}
}
return sum;
}
void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT
{
if (&otherLoop == this) {
return;
}
MIJIN_ASSERT_FATAL(impl::gCurrentTask != nullptr, "Trying to call transferCurrentTask() while not running a task!");
// now start the transfer, first disown the task
StoredTask storedTask = std::move(*impl::gCurrentTask);
impl::gCurrentTask->task = nullptr; // just to be sure
// then send it over to the other loop
otherLoop.addStoredTask(std::move(storedTask));
}
void MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT
{
storedTask.task->setLoop(this);
// just assume we are not on the manager thread, as that wouldn't make sense
queuedTasks_.push(std::move(storedTask));
}
void MultiThreadedTaskLoop::start(std::size_t numWorkerThreads)
{
managerThread_ = std::jthread([this](std::stop_token stopToken) { managerThread(std::move(stopToken)); });
workerThreads_.reserve(numWorkerThreads);
for (std::size_t workerId = 0; workerId < numWorkerThreads; ++workerId) {
workerThreads_.emplace_back([this, workerId](std::stop_token stopToken) { workerThread(std::move(stopToken), workerId); });
}
}
void MultiThreadedTaskLoop::stop()
{
workerThreads_.clear(); // will also set the stop token
managerThread_ = {}; // this too
}
} // namespace mijin

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,10 @@
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
#include <optional>
#include <exception>
#include <memory>
#include <optional>
#include <tuple>
#include <type_traits>
#include "./signal.hpp"
#include "../container/optional.hpp"
@ -26,47 +28,118 @@ namespace mijin
//
// public types
//
template<typename TValue>
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 TValue, template<typename> typename TAllocator, bool exceptions>
class Future
{
private:
impl::FutureStorage<TValue> value_;
bool isSet_ = false;
impl::FutureStorage<TValue, exceptions> value_;
public:
Future() = default;
Future(const Future&) = delete;
Future(Future&&) MIJIN_NOEXCEPT = default;
explicit Future(TAllocator<void> allocator) : sigSet(std::move(allocator)) {}
public:
Future& operator=(const Future&) = delete;
Future& operator=(Future&&) MIJIN_NOEXCEPT = default;
@ -78,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 {
@ -89,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 {
@ -102,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 {
@ -125,17 +202,79 @@ 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
Signal<> sigSet;
BaseSignal<TAllocator> sigSet;
};
template<typename TValue>
using FuturePtr = std::shared_ptr<Future<TValue>>;
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 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
//
namespace impl
{
template<typename... TResult>
struct MultiFutureHelper
{
template<std::size_t... indices>
static bool allReady(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
{
return (std::get<indices>(futures)->ready() && ...);
}
template<std::size_t... indices>
static std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
{
return std::make_tuple(std::move(std::get<indices>(futures)->get())...);
}
};
}
template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
constexpr FuturePtr<T> makeSharedFuture(TAllocator<Future<T>> allocator = {}) MIJIN_NOEXCEPT
{
return std::allocate_shared<Future<T>>(std::move(allocator));
}
template<typename... TResult>
constexpr bool allReady(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
{
return impl::MultiFutureHelper<TResult...>::allReady(futures, std::index_sequence_for<TResult...>());
}
template<typename... TResult>
constexpr std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
{
return impl::MultiFutureHelper<TResult...>::getAll(futures, std::index_sequence_for<TResult...>());
}
} // namespace mijin
#endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)

View File

@ -79,15 +79,20 @@ class MessageQueue
public:
using message_t = TMessage;
using iterator_t = MessageQueueIterator<MessageQueue<TMessage, bufferSize>>;
static constexpr std::size_t BUFFER_SIZE = bufferSize;
private:
std::array<TMessage, bufferSize> messages;
mijin::BitArray<bufferSize, true> messageReady;
std::atomic_uint writePos = 0;
std::atomic_uint readPos = 0;
std::array<TMessage, bufferSize> messages_;
mijin::BitArray<bufferSize, true> messageReady_;
std::atomic_uint writePos_ = 0;
std::atomic_uint readPos_ = 0;
public:
MessageQueue() = default;
MessageQueue(const MessageQueue&) = delete;
MessageQueue(MessageQueue&&) = delete;
explicit MessageQueue(const std::array<TMessage, bufferSize>& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<TMessage>)
: messages_(messages) {}
explicit MessageQueue(std::array<TMessage, bufferSize>&& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TMessage>)
: messages_(std::move(messages)) {}
MessageQueue& operator=(const MessageQueue&) = delete;
MessageQueue& operator=(MessageQueue&&) = delete;
@ -118,22 +123,22 @@ struct TaskMessageQueue
template<typename TMessage, std::size_t bufferSize>
bool MessageQueue<TMessage, bufferSize>::tryPushMaybeMove(TMessage& message)
{
unsigned oldWritePos = writePos.load(std::memory_order_relaxed);
unsigned oldWritePos = writePos_.load(std::memory_order_relaxed);
unsigned newWritePos = 0;
do
{
newWritePos = (oldWritePos + 1) % bufferSize;
if (newWritePos == readPos) {
if (newWritePos == readPos_) {
return false;
}
} while (!writePos.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
} while (!writePos_.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed));
while (messageReady.get(oldWritePos)) {
while (messageReady_.get(oldWritePos)) {
std::this_thread::yield(); // someone is still reading, wait...
}
messages[oldWritePos] = std::move(message);
messageReady.set(oldWritePos, true);
messages_[oldWritePos] = std::move(message);
messageReady_.set(oldWritePos, true);
return true;
}
@ -149,22 +154,22 @@ void MessageQueue<TMessage, bufferSize>::push(TMessage message)
template<typename TMessage, std::size_t bufferSize>
std::optional<TMessage> MessageQueue<TMessage, bufferSize>::tryPop()
{
unsigned oldReadPos = readPos.load(std::memory_order_relaxed);
unsigned oldReadPos = readPos_.load(std::memory_order_relaxed);
unsigned newReadPos = 0;
do
{
if (oldReadPos == writePos) {
if (oldReadPos == writePos_) {
return std::nullopt;
}
newReadPos = (oldReadPos + 1) % bufferSize;
} while (!readPos.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
} while (!readPos_.compare_exchange_weak(oldReadPos, newReadPos, std::memory_order_release, std::memory_order_relaxed));
while (!messageReady.get(oldReadPos)) {
while (!messageReady_.get(oldReadPos)) {
std::this_thread::yield(); // no harm in busy-waiting here, should be fast
};
TMessage message = std::move(messages[oldReadPos]);
messageReady.set(oldReadPos, false);
TMessage message = std::move(messages_[oldReadPos]);
messageReady_.set(oldReadPos, false);
return message;
}

View File

@ -33,11 +33,11 @@ inline constexpr signal_token_t INVALID_SIGNAL_TOKEN = std::numeric_limits<signa
MIJIN_DEFINE_FLAG(Oneshot);
template<typename... TArgs>
class Signal
template<template<typename> typename TAllocator, typename... TArgs>
class BaseSignal
{
public:
using handler_t = std::function<void(TArgs...)>;
using handler_t = std::function<void(TArgs...)>; // TODO: write a custom function wrapper with allocator support
using token_t = signal_token_t;
private:
struct RegisteredHandler
@ -47,36 +47,41 @@ private:
token_t token;
Oneshot oneshot = Oneshot::NO;
};
using handler_vector_t = std::vector<RegisteredHandler>;
using handler_vector_t = std::vector<RegisteredHandler, TAllocator<RegisteredHandler>>;
private:
handler_vector_t handlers_;
token_t nextToken = 1;
std::mutex handlersMutex_;
public:
Signal() = default;
Signal(const Signal&) = delete;
Signal(Signal&&) MIJIN_NOEXCEPT = default;
explicit BaseSignal(TAllocator<void> allocator = {}) : handlers_(TAllocator<RegisteredHandler>(std::move(allocator))) {}
BaseSignal(const BaseSignal&) = delete;
BaseSignal(BaseSignal&&) MIJIN_NOEXCEPT = default;
public:
Signal& operator=(const Signal&) = delete;
Signal& operator=(Signal&&) MIJIN_NOEXCEPT = default;
BaseSignal& operator=(const BaseSignal&) = delete;
BaseSignal& operator=(BaseSignal&&) MIJIN_NOEXCEPT = default;
public:
template<typename THandler, typename TWeak = void>
inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
template<typename TObject, typename TWeak = void>
inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
template<typename TObject, typename TWeak = void>
inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
inline void disconnect(token_t token) MIJIN_NOEXCEPT;
template<typename... TArgs2>
inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT;
};
template<typename... TArgs>
using Signal = BaseSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
//
// public functions
//
template<typename... TArgs>
template<template<typename> typename TAllocator, typename... TArgs>
template<typename THandler, typename TWeak>
inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
inline auto BaseSignal<TAllocator, TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{
std::lock_guard lock(handlersMutex_);
@ -91,9 +96,9 @@ inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::we
return nextToken++;
}
template<typename... TArgs>
template<template<typename> typename TAllocator, typename... TArgs>
template<typename TObject, typename TWeak>
inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
inline auto BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{
std::lock_guard lock(handlersMutex_);
@ -111,8 +116,28 @@ inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)
return nextToken++;
}
template<typename... TArgs>
inline void Signal<TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
template<template<typename> typename TAllocator, typename... TArgs>
template<typename TObject, typename TWeak>
inline auto BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_NOEXCEPT -> token_t
{
std::lock_guard lock(handlersMutex_);
auto callable = [object = &object, handler](TArgs... args)
{
std::invoke(handler, object, std::forward<TArgs>(args)...);
};
handlers_.push_back({
.callable = std::move(callable),
.referenced = std::move(referenced),
.token = nextToken,
.oneshot = oneshot
});
return nextToken++;
}
template<template<typename> typename TAllocator, typename... TArgs>
inline void BaseSignal<TAllocator, TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
{
std::lock_guard lock(handlersMutex_);
@ -123,9 +148,9 @@ inline void Signal<TArgs...>::disconnect(token_t token) MIJIN_NOEXCEPT
handlers_.erase(it, handlers_.end());
}
template<typename... TArgs>
template<template<typename> typename TAllocator, typename... TArgs>
template<typename... TArgs2>
inline void Signal<TArgs...>::emit(TArgs2&&... args) MIJIN_NOEXCEPT
inline void BaseSignal<TAllocator, TArgs...>::emit(TArgs2&&... args) MIJIN_NOEXCEPT
{
std::lock_guard lock(handlersMutex_);

View File

@ -16,8 +16,26 @@ namespace mijin
//
#if !defined(MIJIN_BOXED_OBJECT_DEBUG)
#if defined(MIJIN_DEBUG)
#define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG
#else
#define MIJIN_BOXED_OBJECT_DEBUG 0
#endif
#endif // !defined(MIJIN_BOXED_OBJECT_DEBUG)
#define MIJIN_BOXED_PROXY_FUNC(funcname) \
template<typename... TFuncArgs> \
decltype(auto) funcname(TFuncArgs&&... args) \
{ \
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
}
#define MIJIN_BOXED_PROXY_FUNC_CONST(funcname) \
template<typename... TFuncArgs> \
decltype(auto) funcname(TFuncArgs&&... args) const \
{ \
return base_t::get().funcname(std::forward<TFuncArgs>(args)...); \
}
//
// public constants
@ -39,8 +57,8 @@ private:
bool constructed = false;
#endif
public:
BoxedObject() noexcept : placeholder_() {};
explicit BoxedObject(T object)
constexpr BoxedObject() MIJIN_NOEXCEPT : placeholder_() {};
explicit constexpr BoxedObject(T object) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
#if MIJIN_BOXED_OBJECT_DEBUG
: constructed(true)
#endif
@ -51,7 +69,7 @@ public:
BoxedObject(BoxedObject&&) = delete;
#if MIJIN_BOXED_OBJECT_DEBUG
~BoxedObject()
constexpr ~BoxedObject() noexcept
{
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!");
}
@ -60,13 +78,13 @@ public:
BoxedObject& operator=(const BoxedObject&) = delete;
BoxedObject& operator=(BoxedObject&&) = delete;
T& operator*() noexcept { return get(); }
const T& operator*() const noexcept { return get(); }
T* operator->() noexcept { return &get(); }
const T* operator->() const noexcept { return &get(); }
constexpr T& operator*() noexcept { return get(); }
constexpr const T& operator*() const noexcept { return get(); }
constexpr T* operator->() noexcept { return &get(); }
constexpr const T* operator->() const noexcept { return &get(); }
template<typename... TArgs>
void construct(TArgs&&... args)
constexpr void construct(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(!constructed, "BoxedObject::construct(): Attempt to construct an already constructed object!");
@ -75,7 +93,7 @@ public:
std::construct_at(&object_, std::forward<TArgs>(args)...);
}
void destroy()
constexpr void destroy() MIJIN_NOEXCEPT
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::destroy(): Attempt to destroy a not constructed object!");
@ -83,8 +101,8 @@ public:
#endif
std::destroy_at(&object_);
}
void copyTo(BoxedObject& other) const
constexpr void copyTo(BoxedObject& other) const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!");
@ -92,7 +110,7 @@ public:
other.construct(object_);
}
void moveTo(BoxedObject& other)
constexpr void moveTo(BoxedObject& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!");
@ -101,7 +119,7 @@ public:
destroy();
}
[[nodiscard]] T& get()
[[nodiscard]] constexpr T& get() MIJIN_NOEXCEPT
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!");
@ -109,7 +127,7 @@ public:
return object_;
}
[[nodiscard]] const T& get() const
[[nodiscard]] constexpr const T& get() const MIJIN_NOEXCEPT
{
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!");
@ -131,38 +149,38 @@ private:
BoxedObject<T>* end_ = nullptr;
#endif
public:
BoxedObjectIterator() = default;
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end)
BoxedObjectIterator() noexcept = default;
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* end) MIJIN_NOEXCEPT
: box_(box)
#if MIJIN_CHECKED_ITERATORS
, start_(start), end_(end)
#endif
{}
BoxedObjectIterator(const BoxedObjectIterator&) = default;
BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default;
constexpr BoxedObjectIterator(const BoxedObjectIterator&) noexcept = default;
constexpr BoxedObjectIterator(BoxedObjectIterator&&) noexcept = default;
BoxedObjectIterator& operator=(const BoxedObjectIterator&) = default;
BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default;
BoxedObjectIterator& operator+=(difference_type diff);
BoxedObjectIterator& operator-=(difference_type diff) { return (*this += -diff); }
constexpr BoxedObjectIterator& operator=(const BoxedObjectIterator&) noexcept = default;
constexpr BoxedObjectIterator& operator=(BoxedObjectIterator&&) noexcept = default;
constexpr BoxedObjectIterator& operator+=(difference_type diff) MIJIN_NOEXCEPT;
constexpr BoxedObjectIterator& operator-=(difference_type diff) MIJIN_NOEXCEPT { return (*this += -diff); }
constexpr auto operator<=>(const BoxedObjectIterator& other) const noexcept = default;
[[nodiscard]] T& operator*() const;
[[nodiscard]] T* operator->() const;
BoxedObjectIterator& operator++();
BoxedObjectIterator operator++(int);
BoxedObjectIterator& operator--();
BoxedObjectIterator operator--(int);
[[nodiscard]] constexpr T& operator*() const MIJIN_NOEXCEPT;
[[nodiscard]] constexpr T* operator->() const MIJIN_NOEXCEPT;
constexpr BoxedObjectIterator& operator++() MIJIN_NOEXCEPT;
constexpr BoxedObjectIterator operator++(int) MIJIN_NOEXCEPT;
constexpr BoxedObjectIterator& operator--() MIJIN_NOEXCEPT;
constexpr BoxedObjectIterator operator--(int) MIJIN_NOEXCEPT;
[[nodiscard]] difference_type operator-(const BoxedObjectIterator& other) const { return box_ - other.box_; }
[[nodiscard]] BoxedObjectIterator operator+(difference_type diff) const;
[[nodiscard]] BoxedObjectIterator operator-(difference_type diff) const { return (*this + -diff); }
[[nodiscard]] constexpr difference_type operator-(const BoxedObjectIterator& other) const MIJIN_NOEXCEPT { return box_ - other.box_; }
[[nodiscard]] constexpr BoxedObjectIterator operator+(difference_type diff) const MIJIN_NOEXCEPT;
[[nodiscard]] constexpr BoxedObjectIterator operator-(difference_type diff) const MIJIN_NOEXCEPT { return (*this + -diff); }
[[nodiscard]] T& operator[](difference_type diff) const { return *(*this + diff); }
[[nodiscard]] T& operator[](difference_type diff) const MIJIN_NOEXCEPT { return *(*this + diff); }
};
template<typename T>
inline BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) {
constexpr BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) MIJIN_NOEXCEPT {
return iter + diff;
}
static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
@ -172,7 +190,7 @@ static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
//
template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff)
constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff) MIJIN_NOEXCEPT
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(diff <= (end_ - box_) && diff >= (box_ - start_), "BoxedObjectIterator::operator+=(): Attempt to iterate out of range.");
@ -182,7 +200,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator+=(difference_type diff)
}
template<typename T>
T& BoxedObjectIterator<T>::operator*() const
constexpr T& BoxedObjectIterator<T>::operator*() const MIJIN_NOEXCEPT
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ >= start_ && box_ < end_, "BoxedObjectIterator::operator->(): Attempt to dereference out-of-range iterator.");
@ -191,7 +209,7 @@ T& BoxedObjectIterator<T>::operator*() const
}
template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++() MIJIN_NOEXCEPT
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end.");
@ -200,7 +218,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
}
template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int)
constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int) MIJIN_NOEXCEPT
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(int): Attempt to iterator past the end.");
@ -211,7 +229,7 @@ BoxedObjectIterator<T> BoxedObjectIterator<T>::operator++(int)
}
template<typename T>
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
constexpr BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--() MIJIN_NOEXCEPT
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start.");
@ -220,7 +238,7 @@ BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
}
template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int)
constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int) MIJIN_NOEXCEPT
{
#if MIJIN_CHECKED_ITERATORS
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(int): Attempt to iterator past start.");
@ -231,7 +249,7 @@ BoxedObjectIterator<T> BoxedObjectIterator<T>::operator--(int)
}
template<typename T>
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const
constexpr BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const MIJIN_NOEXCEPT
{
BoxedObjectIterator copy(*this);
copy += diff;

View File

@ -5,10 +5,10 @@
#define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1
#include <bit>
#include <cstddef>
#include <span>
#include <string_view>
#include "../debug/assert.hpp"
#include "../util/traits.hpp"
#include "../internal/common.hpp"
namespace mijin
{
@ -25,60 +25,172 @@ namespace mijin
// public types
//
template<typename TBytes>
class MemoryViewBase
template<typename T>
concept MemoryViewable = requires(const T& object)
{
{ object.data() } -> std::convertible_to<const void*>;
{ object.byteSize() } -> std::convertible_to<std::size_t>;
};
template<typename T>
concept RWMemoryViewable = MemoryViewable<T> && requires(T& object)
{
{ object.data() } -> std::convertible_to<void*>;
};
template<typename TConcrete>
class MixinMemoryView
{
public:
static constexpr bool WRITABLE = requires(TConcrete& object) { { object.data() } -> std::convertible_to<void*>; };
template<typename T>
[[nodiscard]]
auto makeSpan();
template<typename T>
[[nodiscard]]
auto makeSpan() const;
template<typename TChar = char, typename TTraits = std::char_traits<TChar>>
[[nodiscard]]
std::basic_string_view<TChar, TTraits> makeStringView() const ;
template<typename T>
[[nodiscard]]
auto& dataAt(std::size_t offset) MIJIN_NOEXCEPT;
template<typename T>
[[nodiscard]]
auto& dataAt(std::size_t offset) const MIJIN_NOEXCEPT;
[[nodiscard]]
auto bytes() MIJIN_NOEXCEPT
{
using return_t = mijin::copy_const_t<std::remove_pointer_t<decltype(static_cast<TConcrete*>(this)->data())>, std::byte>;
return static_cast<return_t*>(static_cast<TConcrete*>(this)->data());
}
[[nodiscard]]
auto bytes() const MIJIN_NOEXCEPT
{
using return_t = mijin::copy_const_t<std::remove_pointer_t<decltype(static_cast<const TConcrete*>(this)->data())>, std::byte>;
return static_cast<return_t*>(static_cast<const TConcrete*>(this)->data());
}
private:
std::size_t byteSizeImpl() const MIJIN_NOEXCEPT
{
return static_cast<const TConcrete*>(this)->byteSize();
}
};
class MemoryView : public MixinMemoryView<MemoryView>
{
public:
using size_type = std::size_t;
private:
std::span<TBytes> bytes_;
void* data_ = nullptr;
std::size_t byteSize_ = 0;
public:
MemoryViewBase() noexcept = default;
MemoryViewBase(const MemoryViewBase&) noexcept = default;
MemoryViewBase(copy_const_t<TBytes, void>* data, std::size_t size) noexcept : bytes_(static_cast<TBytes*>(data), size) {}
MemoryView() noexcept = default;
MemoryView(const MemoryView&) = default;
MemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {}
template<typename T> requires(!std::is_const_v<T>)
MemoryView(std::span<T> span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {}
template<RWMemoryViewable T>
MemoryView(T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {}
MemoryViewBase& operator=(const MemoryViewBase&) noexcept = default;
[[nodiscard]] void* data() noexcept { return bytes_.data(); }
[[nodiscard]] const void* data() const noexcept { return bytes_.data(); }
[[nodiscard]] size_type byteSize() const noexcept { return bytes_.size(); }
[[nodiscard]] bool empty() const noexcept { return bytes_.empty(); }
MemoryView& operator=(const MemoryView&) = default;
template<typename T>
[[nodiscard]] std::span<T> makeSpan();
[[nodiscard]]
void* data() const MIJIN_NOEXCEPT { return data_; }
template<typename T>
[[nodiscard]] std::span<const T> makeSpan() const;
[[nodiscard]]
size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; }
};
class ConstMemoryView : public MixinMemoryView<ConstMemoryView>
{
public:
using size_type = std::size_t;
private:
const void* data_;
std::size_t byteSize_;
public:
ConstMemoryView() noexcept = default;
ConstMemoryView(const ConstMemoryView&) = default;
ConstMemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {}
template<typename T>
ConstMemoryView(std::span<T> span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {}
template<MemoryViewable T>
ConstMemoryView(const T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {}
ConstMemoryView& operator=(const ConstMemoryView& other) MIJIN_NOEXCEPT = default;
[[nodiscard]]
const void* data() const MIJIN_NOEXCEPT { return data_; }
[[nodiscard]]
size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; }
};
using MemoryView = MemoryViewBase<std::byte>;
using ConstMemoryView = MemoryViewBase<const std::byte>;
//
// public functions
//
template<typename TBytes>
template<typename TConcrete>
template<typename T>
std::span<T> MemoryViewBase<TBytes>::makeSpan()
auto MixinMemoryView<TConcrete>::makeSpan()
{
static_assert(!std::is_const_v<TBytes>, "Cannot create writable spans from const memory views.");
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type.");
return {
std::bit_cast<T*>(bytes_.data()),
std::bit_cast<T*>(bytes_.data() + bytes_.size())
MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return std::span<return_t>{
std::bit_cast<return_t*>(bytes()),
std::bit_cast<return_t*>(bytes() + byteSizeImpl())
};
}
template<typename TBytes>
template<typename TConcrete>
template<typename T>
std::span<const T> MemoryViewBase<TBytes>::makeSpan() const
auto MixinMemoryView<TConcrete>::makeSpan() const
{
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type.");
return {
std::bit_cast<const T*>(bytes_.data()),
std::bit_cast<const T*>(bytes_.data() + bytes_.size())
MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return std::span<return_t>{
std::bit_cast<return_t*>(bytes()),
std::bit_cast<return_t*>(bytes() + byteSizeImpl())
};
}
template<typename TConcrete>
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> MixinMemoryView<TConcrete>::makeStringView() const
{
MIJIN_ASSERT(byteSizeImpl() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type.");
return {std::bit_cast<const TChar*>(bytes()), byteSizeImpl() / sizeof(TChar)};
}
template<typename TConcrete>
template<typename T>
auto& MixinMemoryView<TConcrete>::dataAt(std::size_t offset) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return *std::bit_cast<return_t*>(bytes().data() + offset);
}
template<typename TConcrete>
template<typename T>
auto& MixinMemoryView<TConcrete>::dataAt(std::size_t offset) const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range.");
using return_t = mijin::copy_const_t<decltype(*bytes()), T>;
return *std::bit_cast<return_t*>(bytes().data() + offset);
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED)

View File

@ -10,7 +10,9 @@
#include <span>
#include <string_view>
#include <vector>
#include "./memory_view.hpp"
#include "../debug/assert.hpp"
#include "../internal/common.hpp"
namespace mijin
{
@ -27,17 +29,26 @@ namespace mijin
// public types
//
template<typename T>
template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class BufferView;
class TypelessBuffer
template<template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class BaseTypelessBuffer : public MixinMemoryView<BaseTypelessBuffer<TAllocator>>
{
public:
using size_type = std::size_t;
private:
std::vector<std::byte> bytes_;
std::vector<std::byte, TAllocator<std::byte>> bytes_;
public:
auto operator<=>(const TypelessBuffer&) const noexcept = default;
BaseTypelessBuffer() noexcept = default;
BaseTypelessBuffer(const BaseTypelessBuffer&) = default;
BaseTypelessBuffer(BaseTypelessBuffer&&) = default;
explicit BaseTypelessBuffer(TAllocator<std::byte> allocator) : bytes_(std::move(allocator)) {}
BaseTypelessBuffer& operator=(const BaseTypelessBuffer&) = default;
BaseTypelessBuffer& operator=(BaseTypelessBuffer&&) = default;
auto operator<=>(const BaseTypelessBuffer&) const noexcept = default;
[[nodiscard]] void* data() noexcept { return bytes_.data(); }
[[nodiscard]] const void* data() const noexcept { return bytes_.data(); }
@ -49,27 +60,14 @@ public:
template<typename T>
[[nodiscard]] BufferView<T> makeBufferView() { return BufferView<T>(this); }
template<typename T>
[[nodiscard]] std::span<T> makeSpan();
template<typename T>
[[nodiscard]] std::span<const T> makeSpan() const;
template<typename TChar = char, typename TTraits = std::char_traits<TChar>>
[[nodiscard]] std::basic_string_view<TChar, TTraits> makeStringView() const ;
template<typename T>
void append(std::span<const T> data);
template<typename T>
[[nodiscard]] T& dataAt(size_type offset) MIJIN_NOEXCEPT;
template<typename T>
[[nodiscard]] const T& dataAt(size_type offset) const MIJIN_NOEXCEPT;
};
template<typename T>
using TypelessBuffer = BaseTypelessBuffer<>;
template<typename T, template<typename> typename TAllocator>
class BufferView
{
public:
@ -84,10 +82,10 @@ public:
using iterator = T*;
using const_iterator = const T*;
private:
class TypelessBuffer* buffer_ = nullptr;
class BaseTypelessBuffer<TAllocator>* buffer_ = nullptr;
public:
BufferView() = default;
explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {}
explicit BufferView(class BaseTypelessBuffer<TAllocator>* buffer) : buffer_(buffer) {}
BufferView(const BufferView&) = default;
BufferView& operator=(const BufferView&) = default;
@ -131,57 +129,13 @@ public:
// public functions
//
template<template<typename> typename TAllocator>
template<typename T>
std::span<T> TypelessBuffer::makeSpan()
{
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
return {
std::bit_cast<T*>(bytes_.data()),
std::bit_cast<T*>(bytes_.data() + bytes_.size())
};
}
template<typename T>
std::span<const T> TypelessBuffer::makeSpan() const
{
MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type.");
return {
std::bit_cast<const T*>(bytes_.data()),
std::bit_cast<const T*>(bytes_.data() + bytes_.size())
};
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> TypelessBuffer::makeStringView() const
{
MIJIN_ASSERT(bytes_.size() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type.");
return {std::bit_cast<const TChar*>(bytes_.data()), bytes_.size() / sizeof(TChar)};
}
template<typename T>
void TypelessBuffer::append(std::span<const T> data)
void BaseTypelessBuffer<TAllocator>::append(std::span<const T> data)
{
bytes_.resize(bytes_.size() + data.size_bytes());
std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes());
}
template<typename T>
T& TypelessBuffer::dataAt(size_type offset) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range.");
return *std::bit_cast<T*>(bytes_.data() + offset);
}
template<typename T>
const T& TypelessBuffer::dataAt(size_type offset) const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned.");
MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range.");
return *std::bit_cast<const T*>(bytes_.data() + offset);
}
} // namespace mijin
#endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)

View File

@ -4,8 +4,10 @@
#if !defined(MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED)
#define MIJIN_CONTAINER_VECTOR_MAP_HPP_INCLUDED 1
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "./boxed_object.hpp"
#include "./optional.hpp"
@ -101,7 +103,7 @@ public:
}
};
template<typename TKey, typename TValue, typename TKeyAllocator = std::allocator<TKey>, typename TValueAllocator = std::allocator<TValue>>
template<typename TKey, typename TValue, typename TKeyAllocator = MIJIN_DEFAULT_ALLOCATOR<TKey>, typename TValueAllocator = MIJIN_DEFAULT_ALLOCATOR<TValue>>
class VectorMap
{
public:
@ -118,15 +120,21 @@ private:
std::vector<TKey, TKeyAllocator> keys_;
std::vector<TValue, TValueAllocator> values_;
public:
VectorMap() noexcept = default;
explicit VectorMap(TKeyAllocator keyAllocator = {})
MIJIN_NOEXCEPT_IF((std::is_nothrow_move_constructible_v<TKeyAllocator> && std::is_nothrow_constructible_v<TValueAllocator, const TKeyAllocator&>))
: keys_(std::move(keyAllocator)), values_(TValueAllocator(keys_.get_allocator())) {}
VectorMap(TKeyAllocator keyAllocator, TValueAllocator valueAllocator)
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TKeyAllocator> && std::is_nothrow_move_constructible_v<TValueAllocator>)
: keys_(std::move(keyAllocator)), values_(std::move(valueAllocator)) {}
VectorMap(const VectorMap&) = default;
VectorMap(VectorMap&&) MIJIN_NOEXCEPT = default;
VectorMap(VectorMap&&) = default;
VectorMap& operator=(const VectorMap&) = default;
VectorMap& operator=(VectorMap&&) MIJIN_NOEXCEPT = default;
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())
@ -136,10 +144,21 @@ 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);
}
TValue& operator[](TKey&& key)
{
auto it = find(key);
if (it != end())
{
return it->second;
}
return emplace(std::move(key), TValue()).first->second;
}
[[nodiscard]]
iterator begin() MIJIN_NOEXCEPT { return {keys_.data(), values_.data()}; }
@ -234,7 +253,9 @@ public:
return eraseImpl(idx, count);
}
iterator find(const TKey& key) MIJIN_NOEXCEPT
template<typename TSearch>
[[nodiscard]]
iterator find(const TSearch& key) MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
@ -246,7 +267,9 @@ public:
return end();
}
const_iterator find(const TKey& key) const MIJIN_NOEXCEPT
template<typename TSearch>
[[nodiscard]]
const_iterator find(const TSearch& key) const MIJIN_NOEXCEPT
{
for (std::size_t idx = 0; idx < keys_.size(); ++idx)
{
@ -257,6 +280,12 @@ public:
}
return end();
}
[[nodiscard]]
bool contains(const TKey& key) const MIJIN_NOEXCEPT
{
return std::ranges::contains(keys_, key);
}
private:
iterator eraseImpl(std::ptrdiff_t idx, std::ptrdiff_t count = 1) MIJIN_NOEXCEPT
{

View File

@ -45,7 +45,7 @@ namespace mijin
#if MIJIN_DEBUG
#define MIJIN_RAISE_ERROR(msg, source_loc) \
#define MIJIN_RAISE_ERROR(msg, source_loc) \
switch (mijin::handleError(msg, source_loc)) \
{ \
case mijin::ErrorHandling::CONTINUE: \
@ -99,10 +99,10 @@ if (!static_cast<bool>(condition)) \
MIJIN_FATAL("Debug assertion failed: " #condition "\nMessage: " msg); \
}
#else // MIJIN_DEBUG
#define MIJIN_ERROR(...)
#define MIJIN_ERROR(...) ((void)0)
#define MIJIN_FATAL(...) std::abort()
#define MIJIN_ASSERT(...)
#define MIJIN_ASSERT_FATAL(...)
#define MIJIN_ASSERT(...) ((void)0)
#define MIJIN_ASSERT_FATAL(...) ((void)0)
#endif // !MIJIN_DEBUG
//

View File

@ -4,15 +4,24 @@
#include <optional>
#include <string>
#include "../detect.hpp"
#include "../util/string.hpp"
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC
#if MIJIN_TARGET_OS != MIJIN_OS_WINDOWS && (MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC)
#define MIJIN_USE_LIBBACKTRACE 1
#else
#define MIJIN_USE_LIBBACKTRACE 0
#endif
#if MIJIN_USE_LIBBACKTRACE
#include <backtrace.h>
#include <backtrace.h>
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#include <array>
#include <cstddef>
#include <mutex>
#include <Windows.h>
#include <DbgHelp.h>
#include "../util/winundef.hpp"
#pragma comment(lib, "dbghelp")
#endif
@ -32,11 +41,15 @@ namespace
// internal types
//
#if MIJIN_USE_LIBBACKTRACE
struct BacktraceData
{
std::optional<std::string> error;
std::vector<Stackframe> stackframes;
};
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
HANDLE gProcessHandle = nullptr;
#endif
//
// internal variables
@ -44,6 +57,11 @@ struct BacktraceData
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
std::mutex gDbgHelpMutex;
bool gDbgHelpInitCalled = false;
#endif
//
// internal functions
//
@ -68,6 +86,49 @@ void backtraceErrorCallback(void* data, const char* msg, int /* errnum */)
}
thread_local backtrace_state* gBacktraceState = nullptr;
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
void cleanupDbgHelp() MIJIN_NOEXCEPT
{
if (!SymCleanup(gProcessHandle))
{
[[maybe_unused]] const DWORD error = GetLastError();
MIJIN_ERROR("Error cleaning up DbgHelp.");
}
}
[[nodiscard]]
bool initDbgHelp() MIJIN_NOEXCEPT
{
if (gDbgHelpInitCalled)
{
return gProcessHandle != nullptr; // if init was successful, process handle is not null
}
gDbgHelpInitCalled = true;
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
HANDLE hCurrentProcess = GetCurrentProcess();
HANDLE hCopy = nullptr;
if (!DuplicateHandle(hCurrentProcess, hCurrentProcess, hCurrentProcess, &hCopy, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
[[maybe_unused]] const DWORD error = GetLastError();
MIJIN_ERROR("Error duplicating process handle.");
return false;
}
if (!SymInitialize(hCopy, nullptr, true))
{
[[maybe_unused]] const DWORD error = GetLastError();
MIJIN_ERROR("Error initializing DbHelp.");
return false;
}
[[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
gProcessHandle = hCopy;
return true;
}
#endif // MIJIN_USE_LIBBACKTRACE
} // namespace
@ -98,9 +159,87 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) MIJIN_NOEXCEPT
}
return Stacktrace(std::move(btData.stackframes));
#else // MIJIN_USE_LIBBACKTRACE
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
if (!initDbgHelp())
{
return ResultError("error initializing DbgHelp");
}
const HANDLE hThread = GetCurrentThread();
CONTEXT context;
RtlCaptureContext(&context);
STACKFRAME64 stackFrame = {
.AddrPC = {
.Offset = context.Rip,
.Mode = AddrModeFlat
},
.AddrFrame = {
.Offset = context.Rbp,
.Mode = AddrModeFlat
},
.AddrStack = {
.Offset = context.Rsp,
.Mode = AddrModeFlat
}
};
++skipFrames; // always skip the first frame (the current function)
// for symbol info
DWORD64 displacement64 = 0;
static constexpr std::size_t SYMBOL_BUFFER_SIZE = sizeof(SYMBOL_INFO) + (MAX_SYM_NAME * sizeof(char));
std::array<std::byte, SYMBOL_BUFFER_SIZE> symbolBuffer alignas(SYMBOL_INFO);
SYMBOL_INFO& symbolInfo = *std::bit_cast<SYMBOL_INFO*>(symbolBuffer.data());
symbolInfo.SizeOfStruct = sizeof(SYMBOL_BUFFER_SIZE);
symbolInfo.MaxNameLen = MAX_SYM_NAME;
// for file and line info
DWORD displacement = 0;
IMAGEHLP_LINE64 line64;
line64.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
std::vector<Stackframe> stackframes;
while (StackWalk64(
/* MachineType = */ IMAGE_FILE_MACHINE_AMD64,
/* hProcess = */ gProcessHandle,
/* hThread = */ hThread,
/* StackFrame = */ &stackFrame,
/* ContextRecord = */ &context,
/* ReadMemoryRoutine = */ nullptr,
/* FunctionTableAccessRoutine = */ &SymFunctionTableAccess64,
/* GetModuleBaseRoutine = */ &SymGetModuleBase64,
/* TranslateAddress = */ nullptr
))
{
if (skipFrames > 0)
{
--skipFrames;
continue;
}
Stackframe& frame = stackframes.emplace_back();
const DWORD64 baseAddress = SymGetModuleBase64(gProcessHandle, stackFrame.AddrPC.Offset);
const DWORD64 relativeAddress = stackFrame.AddrPC.Offset - baseAddress;
frame.address = std::bit_cast<void*>(relativeAddress);
if (SymFromAddr(gProcessHandle, stackFrame.AddrPC.Offset, &displacement64, &symbolInfo))
{
frame.function = symbolInfo.Name;
}
if (SymGetLineFromAddr64(gProcessHandle, stackFrame.AddrPC.Offset, &displacement, &line64))
{
frame.filename = line64.FileName;
frame.lineNumber = static_cast<int>(line64.LineNumber);
}
}
return Stacktrace(std::move(stackframes));
#else // MIJIN_USE_LIBBACKTRACE || (MIJIN_TARGET_OS == MIJIN_OS_WINDOWS)
(void) skipFrames;
return {}; // TODO
return ResultError("not implemented");
#endif // MIJIN_USE_LIBBACKTRACE
}

View File

@ -5,6 +5,7 @@
#define MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED 1
#include <cmath>
#include <format>
#include <iomanip>
#include <vector>
#if __has_include(<fmt/format.h>)
@ -87,6 +88,68 @@ TStream& operator<<(TStream& stream, const Stacktrace& stacktrace)
} // namespace mijin
template<typename TChar>
struct std::formatter<mijin::Stackframe, TChar>
{
using char_t = TChar;
template<typename 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::Stackframe& stackframe, TContext& ctx) const
{
auto it = ctx.out();
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "[{}] {}:{} in {}"), stackframe.address, stackframe.filename,
stackframe.lineNumber, mijin::demangleCPPIdentifier(stackframe.function.c_str()));
return it;
}
};
template<typename TChar>
struct std::formatter<mijin::Stacktrace, 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::Stacktrace& stacktrace, TContext& ctx) const
{
const int numDigits = static_cast<int>(std::ceil(std::log10(stacktrace.getFrames().size())));
auto it = ctx.out();
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "[{} frames]"), stacktrace.getFrames().size());
for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames()))
{
it = std::format_to(it, MIJIN_SMART_QUOTE(char_t, "\n #{:<{}} at {}"), idx, numDigits, frame);
}
return it;
}
};
#if __has_include(<fmt/format.h>)
template<>
struct fmt::formatter<mijin::Stackframe>

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

@ -1,4 +1,7 @@
#pragma once
#include "./config.hpp"
#include "./helpers.hpp"
#include "./exception.hpp"
#include "./version_support.hpp"

View File

@ -0,0 +1,22 @@
#pragma once
#if !defined(MIJIN_INTERNAL_CONFIG_HPP_INCLUDED)
#define MIJIN_INTERNAL_CONFIG_HPP_INCLUDED 1
#define MIJIN_QUOTED_ACTUAL(x) #x
#define MIJIN_QUOTED(x) MIJIN_QUOTED_ACTUAL(x)
#if defined(MIJIN_CONFIG_HEADER)
#include MIJIN_QUOTED(MIJIN_CONFIG_HEADER)
#endif
#if !defined(MIJIN_DEFAULT_ALLOCATOR)
#define MIJIN_DEFAULT_ALLOCATOR std::allocator
#endif
#if !defined(MIJIN_DEFAULT_CHAR_TYPE)
#define MIJIN_DEFAULT_CHAR_TYPE char
#endif
#endif // !defined(MIJIN_INTERNAL_CONFIG_HPP_INCLUDED)

View File

@ -23,10 +23,12 @@
#else
#if defined(MIJIN_TEST_NO_NOEXCEPT) // only use for testing
#define MIJIN_NOEXCEPT
#define MIJIN_NOEXCEPT_IF(x)
#define MIJIN_THROWS
#define MIJIN_CONDITIONAL_NOEXCEPT(...)
#else
#define MIJIN_NOEXCEPT noexcept
#define MIJIN_NOEXCEPT_IF(x) noexcept(x)
#define MIJIN_THROWS noexcept
#define MIJIN_CONDITIONAL_NOEXCEPT(...) noexcept(__VA_ARGS__)
#endif

View File

@ -0,0 +1,57 @@
#pragma once
#if !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED)
#define MIJIN_INTERNAL_HELPERS_HPP_INCLUDED 1
#include <type_traits>
#include "../util/traits.hpp"
#define MIJIN_IDENTITY(what) what
#define MIJIN_NULLIFY(what)
#define MIJIN_SMART_QUOTE(chr_type, text) \
[]<typename TChar__>(TChar__) consteval \
{ \
if constexpr (std::is_same_v<TChar__, char>) \
{ \
return text; \
} \
else if constexpr (std::is_same_v<TChar__, wchar_t>) \
{ \
return L ## text; \
} \
else if constexpr (std::is_same_v<TChar__, char8_t>) \
{ \
return u8 ## text; \
} \
else \
{ \
static_assert(::mijin::always_false_v<TChar__>, "Invalid char type."); \
} \
}(chr_type())
#define MIJIN_SMART_STRINGIFY(chr_type, text) MIJIN_SMART_QUOTE(chr_type, #text)
#define MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, prefix_a, prefix_b, prefix_c, set_args) \
prefix_a prefix_b(char) prefix_c \
using C ## type_name = Base ## type_name <set_args(char)>; \
\
prefix_a prefix_b(wchar_t) prefix_c \
using W ## type_name = Base ## type_name <set_args(wchar_t)>; \
\
prefix_a prefix_b(char8_t) prefix_c \
using U ## type_name = Base ## type_name <set_args(char8_t)>; \
\
using type_name = Base ## type_name<>;
#define MIJIN_DEFINE_CHAR_VERSIONS_TMPL(type_name, remaining_args, set_args) \
MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, template<, remaining_args, >, set_args)
#define MIJIN_DEFINE_CHAR_VERSIONS_CUSTOM(type_name, set_args) \
MIJIN_DEFINE_CHAR_VERSIONS_IMPL(type_name, , MIJIN_NULLIFY, , set_args)
#define MIJIN_DEFINE_CHAR_VERSIONS(type_name) \
MIJIN_DEFINE_CHAR_VERSIONS_CUSTOM(type_name, MIJIN_IDENTITY)
#endif // !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED)

View File

@ -0,0 +1,19 @@
#pragma once
#if !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)
#define MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED 1
#include "../detect.hpp"
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma clang diagnostic ignored "-Wc++26-extensions"
#endif
#if defined(__cpp_deleted_function)
#define MIJIN_DELETE(reason) = delete(reason)
#else
#define MIJIN_DELETE(reason) = delete
#endif
#endif // !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)

View File

@ -113,6 +113,42 @@ StreamError Stream::writeBinaryString(std::string_view str)
return writeSpan(str.begin(), str.end());
}
StreamError Stream::readZString(std::string& outString)
{
char chr = '\0';
std::string result;
while (true)
{
if (isAtEnd())
{
return StreamError::IO_ERROR;
}
if (StreamError error = read(chr); error != StreamError::SUCCESS)
{
return error;
}
if (chr == '\0')
{
outString = std::move(result);
return StreamError::SUCCESS;
}
result.push_back(chr);
}
}
StreamError Stream::writeZString(std::string_view str)
{
static const char ZERO = '\0';
if (StreamError error = writeRaw(str.data(), str.size() * sizeof(char)); error != StreamError::SUCCESS)
{
return error;
}
return write(ZERO);
}
mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString)
{
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
@ -161,76 +197,6 @@ StreamError Stream::getTotalLength(std::size_t& outLength)
return StreamError::SUCCESS;
}
StreamError Stream::readRest(TypelessBuffer& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
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;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
const std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
return StreamError::SUCCESS;
}
mijin::Task<StreamError> Stream::c_readRest(TypelessBuffer& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
std::span<std::byte> bufferBytes = outBuffer.makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
co_return StreamError::SUCCESS;
}
StreamError Stream::readLine(std::string& outString)
{
MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking.");
@ -324,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) {
@ -475,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

@ -132,8 +132,9 @@ public:
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return readRaw(&*range.begin(), bytes, {.partial = partial}, outBytesRead);
}
StreamError readRaw(TypelessBuffer& buffer, const ReadOptions& options = {})
template<template<typename> typename TAllocator>
StreamError readRaw(BaseTypelessBuffer<TAllocator>& buffer, const ReadOptions& options = {})
{
return readRaw(buffer.data(), buffer.byteSize(), options);
}
@ -144,8 +145,9 @@ public:
const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
return c_readRaw(&*range.begin(), bytes, options, outBytesRead);
}
mijin::Task<StreamError> c_readRaw(TypelessBuffer& buffer, const ReadOptions& options = {})
template<template<typename> typename TAllocator>
mijin::Task<StreamError> c_readRaw(BaseTypelessBuffer<TAllocator>& buffer, const ReadOptions& options = {})
{
return c_readRaw(buffer.data(), buffer.byteSize(), options);
}
@ -219,7 +221,7 @@ public:
}
template<typename T>
StreamError write(const T& value) requires(std::is_trivial_v<T>)
StreamError write(const T& value)
{
return writeRaw(&value, sizeof(T));
}
@ -259,6 +261,9 @@ public:
StreamError readBinaryString(std::string& outString);
StreamError writeBinaryString(std::string_view str);
StreamError readZString(std::string& outString);
StreamError writeZString(std::string_view str);
mijin::Task<StreamError> c_readBinaryString(std::string& outString);
mijin::Task<StreamError> c_writeBinaryString(std::string_view str);
@ -269,17 +274,23 @@ public:
inline StreamError writeString(std::string_view str) { return writeBinaryString(str); }
StreamError getTotalLength(std::size_t& outLength);
StreamError readRest(TypelessBuffer& outBuffer);
mijin::Task<StreamError> c_readRest(TypelessBuffer& outBuffer);
StreamError copyTo(Stream& otherStream);
template<template<typename> typename TAllocator>
StreamError readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
template<template<typename> typename TAllocator>
mijin::Task<StreamError> c_readRest(BaseTypelessBuffer<TAllocator>& outBuffer);
StreamError readLine(std::string& outString);
mijin::Task<StreamError> c_readLine(std::string& outString);
template<typename TChar = char>
StreamError readAsString(std::basic_string<TChar>& outString);
template<typename TChar = char, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<char>>
StreamError readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString);
template<typename TChar = char>
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar>& outString);
template<typename TChar = char, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<char>>
mijin::Task<StreamError> c_readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString);
StreamError writeText(std::string_view str)
{
@ -360,8 +371,80 @@ using StreamResult = ResultBase<TSuccess, StreamError>;
// public functions
//
template<typename TChar>
StreamError Stream::readAsString(std::basic_string<TChar>& outString)
template<template<typename> typename TAllocator>
StreamError Stream::readRest(BaseTypelessBuffer<TAllocator>& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
return error;
}
return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
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;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
const std::span<std::byte> bufferBytes = outBuffer.template makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
return StreamError::SUCCESS;
}
template<template<typename> typename TAllocator>
mijin::Task<StreamError> Stream::c_readRest(BaseTypelessBuffer<TAllocator>& outBuffer)
{
// first try to allocate everything at once
std::size_t length = 0;
if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS)
{
MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?");
length -= tell();
outBuffer.resize(length);
if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS)
{
co_return error;
}
co_return StreamError::SUCCESS;
}
// could not determine the size, read chunk-wise
static constexpr std::size_t CHUNK_SIZE = 4096;
std::array<std::byte, CHUNK_SIZE> chunk = {};
while (!isAtEnd())
{
std::size_t bytesRead = 0;
if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS)
{
co_return error;
}
outBuffer.resize(outBuffer.byteSize() + bytesRead);
std::span<std::byte> bufferBytes = outBuffer.template makeSpan<std::byte>();
std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast<long>(bytesRead));
}
co_return StreamError::SUCCESS;
}
template<typename TChar, typename TTraits, typename TAllocator>
StreamError Stream::readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
@ -396,8 +479,8 @@ StreamError Stream::readAsString(std::basic_string<TChar>& outString)
return StreamError::SUCCESS;
}
template<typename TChar>
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar>& outString)
template<typename TChar, typename TTraits, typename TAllocator>
mijin::Task<StreamError> Stream::c_readAsString(std::basic_string<TChar, TTraits, TAllocator>& outString)
{
static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t");
@ -463,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)
@ -471,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

@ -0,0 +1,52 @@
#pragma once
#if !defined(MIJIN_LOGGING_FILTERS_HPP_INCLUDED)
#define MIJIN_LOGGING_FILTERS_HPP_INCLUDED 1
#include "./logger.hpp"
namespace mijin
{
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLevelFilter : public BaseLogFilter<TChar>
{
public:
using base_t = BaseLogFilter<TChar>;
using typename base_t::char_t;
using typename base_t::message_t;
private:
int mMinLevel = 0;
int mMaxLevel = 0;
public:
explicit BaseLevelFilter(int minLevel, int maxLevel = std::numeric_limits<int>::max()) MIJIN_NOEXCEPT
: mMinLevel(minLevel), mMaxLevel(maxLevel) {}
explicit BaseLevelFilter(const BaseLogLevel<char_t>& minLevel, const BaseLogLevel<char_t>& maxLevel = {nullptr, std::numeric_limits<int>::max()}) MIJIN_NOEXCEPT
: mMinLevel(minLevel.value), mMaxLevel(maxLevel.value) {}
[[nodiscard]]
int getMinLevel() const MIJIN_NOEXCEPT { return mMinLevel; }
[[nodiscard]]
int getMaxLevel() const MIJIN_NOEXCEPT { return mMaxLevel; }
void setMinLevel(int level) MIJIN_NOEXCEPT { mMinLevel = level; }
void setMinLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinLevel = level.value; }
void setMaxLevel(int level) MIJIN_NOEXCEPT { mMaxLevel = level; }
void setMaxLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMaxLevel = level.value; }
bool shouldShow(const message_t& message) MIJIN_NOEXCEPT override
{
return message.level->value >= mMinLevel && message.level->value <= mMaxLevel;
}
};
MIJIN_DEFINE_CHAR_VERSIONS(LevelFilter)
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)

View File

@ -0,0 +1,323 @@
#pragma once
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
#include <format>
#include <variant>
#include "./logger.hpp"
#include "../internal/common.hpp"
#include "../memory/dynamic_pointer.hpp"
#include "../memory/memutil.hpp"
#include "../memory/virtual_allocator.hpp"
#include "../util/annot.hpp"
#include "../util/ansi_colors.hpp"
#include "../util/concepts.hpp"
#include "../util/string.hpp"
namespace mijin
{
#define FORMATTER_COMMON_ARGS(chr_type) allocator_type_for<chr_type> TAllocator = MIJIN_DEFAULT_ALLOCATOR<chr_type>
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
FORMATTER_COMMON_ARGS(TChar)>
class BaseLogFormatter
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator;
using string_t = std::basic_string<char_t, traits_t, allocator_t>;
virtual ~BaseLogFormatter() noexcept = default;
virtual void format(const LogMessage& message, string_t& outFormatted) = 0;
};
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
FORMATTER_COMMON_ARGS(TChar)>
class BaseSimpleLogFormatter : public BaseLogFormatter<TChar, TTraits, TAllocator>
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator;
using base_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
using typename base_t::string_t;
using string_view_t = std::basic_string_view<char_t, traits_t>;
private:
string_t mFormat;
string_t mFormatBuffer;
public:
explicit BaseSimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)), mFormatBuffer(mFormat.get_allocator()) {}
void format(const LogMessage& message, string_t& outFormatted) override;
private:
void formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted);
};
#define FORMATTER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(LogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS)
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(SimpleLogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS)
#undef FORMATTER_COMMON_ARGS
#undef FORMATTER_SET_ARGS
#define MIJIN_FORMATTING_SINK_COMMON_ARGS(chr_type) \
typename TTraits = std::char_traits<chr_type>, \
template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, \
deleter_type<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>> TDeleter \
= AllocatorDeleter<TAllocator<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>>>
#define MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT \
typename TChar = MIJIN_DEFAULT_CHAR_TYPE, \
MIJIN_FORMATTING_SINK_COMMON_ARGS(TChar)
#define MIJIN_FORMATTING_SINK_TMP_ARG_NAMES TChar, TTraits, TAllocator, TDeleter
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseFormattingLogSink : public BaseLogSink<TChar>
{
public:
using base_t = BaseLogSink<TChar>;
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator<TChar>;
using formatter_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
using formatter_deleter_t = TDeleter;
using formatter_ptr_t = DynamicPointer<formatter_t, formatter_deleter_t>;
using string_t = formatter_t::string_t;
using typename base_t::message_t;
private:
formatter_ptr_t mFormatter;
string_t mBuffer;
public:
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))
{}
virtual void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT = 0;
void handleMessage(const message_t& message) noexcept override
{
mBuffer.clear();
mFormatter->format(message, mBuffer);
handleMessageFormatted(message, mBuffer.c_str());
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(FormattingLogSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::format(const LogMessage& message, string_t& outFormatted)
{
mFormatBuffer.clear();
for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos)
{
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
{
++pos;
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
{
// double {
outFormatted += MIJIN_SMART_QUOTE(char_t, '{');
continue;
}
const auto argStart = pos;
static const string_view_t endChars = MIJIN_SMART_QUOTE(char_t, ":}");
pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end());
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
const string_view_t argName(argStart, pos);
string_view_t argFormat;
if (*pos == ':')
{
const auto formatStart = pos;
pos = std::find(pos, mFormat.end(), MIJIN_SMART_QUOTE(char_t, '}'));
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
argFormat = string_view_t(formatStart, pos);
}
// small utility that uses the provided string buffer for storing the format string
auto formatInline = [&](const auto& value)
{
using type_t = std::decay_t<decltype(value)>;
// if there is no format, just directly print the value
if (argFormat.empty())
{
if constexpr (is_char_v<type_t>)
{
convertStringType(string_view_t(&value, 1), outFormatted);
}
else if constexpr (std::is_arithmetic_v<type_t>)
{
std::format_to(std::back_inserter(outFormatted), MIJIN_SMART_QUOTE(char_t, "{}"), value);
}
else if constexpr (is_string_v<type_t> || is_cstring_v<type_t>)
{
convertStringType(value, outFormatted);
}
else
{
static_assert(always_false_v<type_t>);
outFormatted += value;
}
return;
}
// first copy the format string + braces into the buffer
const auto formatStart = mFormatBuffer.size();
mFormatBuffer += '{';
mFormatBuffer += argFormat;
mFormatBuffer += '}';
const auto formatEnd = mFormatBuffer.size();
auto doFormatTo = [](string_t& string, string_view_t format, const auto& value)
{
if constexpr (std::is_same_v<char_t, char>)
{
std::vformat_to(std::back_inserter(string), format, std::make_format_args(value));
}
else if constexpr (std::is_same_v<char_t, wchar_t>)
{
std::vformat_to(std::back_inserter(string), format, std::make_wformat_args(value));
}
else
{
static_assert(always_false_v<char_t>, "Cannot format this char type.");
}
};
auto doFormat = [&](const auto& value)
{
auto format = string_view_t(mFormatBuffer).substr(formatStart, formatEnd - formatStart);
doFormatTo(outFormatted, format, value);
};
if constexpr (is_char_v<type_t> && !std::is_same_v<char_t, type_t>)
{
static_assert(always_false_v<type_t>, "TODO...");
}
else if constexpr ((is_string_v<type_t> || is_cstring_v<type_t>) && !std::is_same_v<str_char_type_t<type_t>, char_t>)
{
// different string type, needs to be converted
const auto convertedStart = mFormatBuffer.size();
convertStringType(value, mFormatBuffer);
const auto convertedEnd = mFormatBuffer.size();
// then we can format it
auto converted = string_view_t(mFormatBuffer).substr(convertedStart, mFormatBuffer.size() - convertedEnd);
doFormat(converted);
}
else
{
// nothing special
doFormat(value);
}
};
if (argName == MIJIN_SMART_QUOTE(char_t, "text"))
{
formatInline(message.text);
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "channel"))
{
formatInline(message.channel->name);
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "file"))
{
formatInline(message.sourceLocation.file_name());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "function"))
{
formatInline(message.sourceLocation.function_name());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "line"))
{
formatInline(message.sourceLocation.line());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "column"))
{
formatInline(message.sourceLocation.column());
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "level"))
{
formatInline(message.level->name);
}
else if (argName == MIJIN_SMART_QUOTE(char_t, "ansi"))
{
formatAnsiSequence(message, argFormat.substr(1), outFormatted);
}
else
{
MIJIN_ERROR("Invalid format argument name.");
}
}
else
{
outFormatted += *pos;
}
}
}
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted)
{
std::size_t numParts = 0;
const auto formatParts = splitFixed<4>(ansiName, ",", {}, &numParts);
outFormatted += MIJIN_SMART_QUOTE(char_t, "\033[");
for (std::size_t partIdx = 0; partIdx < numParts; ++partIdx)
{
const string_view_t& part = formatParts[partIdx];
if (partIdx > 0)
{
outFormatted += MIJIN_SMART_QUOTE(char_t, ',');
}
if (part == MIJIN_SMART_QUOTE(char_t, "reset"))
{
outFormatted += BaseAnsiFontEffects<char_t>::RESET;
}
else if (part == MIJIN_SMART_QUOTE(char_t, "level_color"))
{
const int levelValue = message.level->value;
if (levelValue < MIJIN_LOG_LEVEL_VALUE_VERBOSE)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_CYAN;
}
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_INFO)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_WHITE;
}
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_WARNING)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_BRIGHT_WHITE;
}
else if (levelValue < MIJIN_LOG_LEVEL_VALUE_ERROR)
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_YELLOW;
}
else
{
outFormatted += BaseAnsiFontEffects<char_t>::FG_RED;
}
}
else
{
MIJIN_ERROR("Invalid format ansi font effect name.");
}
}
outFormatted += MIJIN_SMART_QUOTE(char_t, 'm');
}
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)

View File

@ -0,0 +1,298 @@
#pragma once
#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
#include <cstdint>
#include <format>
#include <mutex>
#include <source_location>
#include <string>
#include <vector>
#include "../internal/common.hpp"
#include "../util/annot.hpp"
namespace mijin
{
#if !defined(MIJIN_LOG_LEVEL_VALUE_DEBUG)
#define MIJIN_LOG_LEVEL_VALUE_DEBUG -1000
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_VERBOSE)
#define MIJIN_LOG_LEVEL_VALUE_VERBOSE -500
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_INFO)
#define MIJIN_LOG_LEVEL_VALUE_INFO 0
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_WARNING)
#define MIJIN_LOG_LEVEL_VALUE_WARNING 500
#endif
#if !defined(MIJIN_LOG_LEVEL_VALUE_ERROR)
#define MIJIN_LOG_LEVEL_VALUE_ERROR 1000
#endif
#if !defined(MIJIN_FUNCNAME_GET_LOGGER)
#define MIJIN_FUNCNAME_GET_LOGGER mijin__getLogger__
#endif
#if !defined(MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE)
#define MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE mijin__getMinLogLevelCompile
#endif
#if !defined(MIJIN_NSNAME_LOG_LEVEL)
#define MIJIN_NSNAME_LOG_LEVEL mijin_log_level
#endif
#if !defined(MIJIN_NSNAME_LOG_CHANNEL)
#define MIJIN_NSNAME_LOG_CHANNEL mijin_log_channel
#endif
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogLevel
{
using char_t = TChar;
const char_t* name;
int value;
explicit operator int() const MIJIN_NOEXCEPT { return value; }
auto operator<=>(const BaseLogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
};
MIJIN_DEFINE_CHAR_VERSIONS(LogLevel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogChannel
{
using char_t = TChar;
const char_t* name;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogChannel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogMessage
{
using char_t = TChar;
const char_t* text;
const BaseLogChannel<char_t>* channel;
const BaseLogLevel<char_t>* level;
std::source_location sourceLocation;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogMessage)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogSink
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogSink() noexcept = default;
virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogFilter
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogFilter() noexcept = default;
virtual bool shouldShow(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogFilter)
#define LOGGER_COMMON_ARGS(chr_type) template<typename T> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)>
class BaseLogger
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator<char_t>;
using sink_t = BaseLogSink<char_t>;
using filter_t = BaseLogFilter<char_t>;
using level_t = BaseLogLevel<char_t>;
using channel_t = BaseLogChannel<char_t>;
using message_t = BaseLogMessage<char_t>;
using string_t = std::basic_string<char_t, traits_t, allocator_t>;
private:
struct SinkEntry
{
sink_t* sink;
filter_t* filter;
};
std::vector<SinkEntry, TAllocator<SinkEntry>> mSinks;
mutable std::mutex mMutex;
public:
explicit BaseLogger(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<SinkEntry>, TAllocator<void>&&>))
: mSinks(TAllocator<SinkEntry>(std::move(allocator)))
{}
BaseLogger(const BaseLogger&) = default;
BaseLogger(BaseLogger&&) = default;
BaseLogger& operator=(const BaseLogger&) = default;
BaseLogger& operator=(BaseLogger&&) = default;
void addSink(sink_t& sink)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, nullptr});
}
void addSink(sink_t& sink, filter_t& filter)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, &filter});
}
void postMessage(const message_t& message) const MIJIN_NOEXCEPT
{
std::unique_lock _(mMutex);
for (const SinkEntry& entry : mSinks)
{
if (entry.filter != nullptr && !entry.filter->shouldShow(message)) {
continue;
}
entry.sink->handleMessage(message);
}
}
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, const char_t* msg) const MIJIN_NOEXCEPT
{
postMessage({
.text = msg,
.channel = &channel,
.level = &level,
.sourceLocation = std::move(sourceLocation)
});
}
template<typename... TArgs>
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation,
std::basic_format_string<char_t, std::type_identity_t<TArgs>...> fmt, TArgs&& ... args) const
MIJIN_NOEXCEPT_IF(noexcept(std::declval<allocator_t>().allocate(1)))
{
string_t buffer(allocator_t(mSinks.get_allocator()));
std::format_to(std::back_inserter(buffer), fmt, std::forward<TArgs>(args)...);
log(level, channel, std::move(sourceLocation), buffer.c_str());
}
};
#define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(Logger, LOGGER_COMMON_ARGS, LOGGER_SET_ARGS)
#undef LOGGER_COMMON_ARGS
#undef LOGGER_SET_ARGS
#define MIJIN_DECLARE_LOG_CHANNEL_BASE(chr_type, cnlName) \
namespace MIJIN_NSNAME_LOG_CHANNEL \
{ \
extern const ::mijin::BaseLogChannel<chr_type> cnlName; \
}
#define MIJIN_DECLARE_LOG_CHANNEL(cnlName) MIJIN_DECLARE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName)
#define MIJIN_DEFINE_LOG_CHANNEL_BASE(chr_type, cnlName) \
namespace MIJIN_NSNAME_LOG_CHANNEL \
{ \
const ::mijin::BaseLogChannel<chr_type> cnlName { \
.name = MIJIN_SMART_STRINGIFY(chr_type, cnlName) \
}; \
}
#define MIJIN_DEFINE_LOG_CHANNEL(cnlName) MIJIN_DEFINE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName)
#define MIJIN_DEFINE_LOG_LEVEL_BASE(chr_type, lvlName, lvlValue) \
namespace MIJIN_NSNAME_LOG_LEVEL \
{ \
inline constexpr ::mijin::BaseLogLevel<chr_type> lvlName{ \
.name = MIJIN_SMART_STRINGIFY(chr_type, lvlName), \
.value = lvlValue \
}; \
}
#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) MIJIN_DEFINE_LOG_LEVEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, lvlName, lvlValue)
#if defined(MIJIN_MIN_LOGLEVEL_COMPILE)
inline constexpr int MIN_LOG_LEVEL_COMPILE = static_cast<int>(MIJIN_MIN_LOGLEVEL_COMPILE);
#elif defined(MIJIN_DEBUG)
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG;
#else
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE;
#endif
#define MIJIN_DEFINE_DEFAULT_LOG_LEVELS \
MIJIN_DEFINE_LOG_LEVEL(DEBUG, MIJIN_LOG_LEVEL_VALUE_DEBUG) \
MIJIN_DEFINE_LOG_LEVEL(VERBOSE, MIJIN_LOG_LEVEL_VALUE_VERBOSE) \
MIJIN_DEFINE_LOG_LEVEL(INFO, MIJIN_LOG_LEVEL_VALUE_INFO) \
MIJIN_DEFINE_LOG_LEVEL(WARNING, MIJIN_LOG_LEVEL_VALUE_WARNING) \
MIJIN_DEFINE_LOG_LEVEL(ERROR, MIJIN_LOG_LEVEL_VALUE_ERROR)
#define MIJIN_LOG_LEVEL_OBJECT(level) MIJIN_NSNAME_LOG_LEVEL::level
#define MIJIN_LOG_CHANNEL_OBJECT(channel) MIJIN_NSNAME_LOG_CHANNEL::channel
#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, ...) \
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 \
{ \
return loggerExpr; \
}
#define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \
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 \
{ \
return MIJIN_LOG_LEVEL_OBJECT(level).value; \
}
#define MIJIN_SET_SCOPE_MIN_LOG_LEVEL_COMPILE(level) \
auto MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE = []() -> int \
{ \
return MIJIN_LOG_LEVEL_OBJECT(level).value; \
};
} // namespace mijin
inline constexpr int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT
{
return ::mijin::MIN_LOG_LEVEL_COMPILE;
}
#endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)

View File

@ -0,0 +1,71 @@
#pragma once
#if !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseStdioSink : 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;
private:
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
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)) {}
[[nodiscard]]
int getMinStderrLevel() const MIJIN_NOEXCEPT { return mMinStderrLevel; }
void setMinStderrLevel(int level) MIJIN_NOEXCEPT { mMinStderrLevel = level; }
void setMinStderrLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinStderrLevel = level.value; }
void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT override
{
FILE* stream = (message.level->value >= mMinStderrLevel) ? stderr : stdout;
if constexpr (std::is_same_v<char_t, char>)
{
std::fputs(formatted, stream);
std::fputc('\n', stream);
}
else if constexpr (std::is_same_v<char_t, wchar_t>)
{
std::fputws(formatted, stream);
std::fputwc(L'\n', stream);
}
else if constexpr (sizeof(char_t) == sizeof(char))
{
// char8_t etc.
std::fputs(std::bit_cast<const char*>(formatted), stream);
std::fputc('\n', stream);
}
else
{
static_assert(always_false_v<char_t>, "Character type not supported.");
}
std::fflush(stream);
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StdioSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)

View File

@ -0,0 +1,58 @@
#pragma once
#if !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../io/stream.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseStreamSink : 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;
using stream_ptr_t = DynamicPointer<Stream>;
private:
stream_ptr_t mStream;
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
explicit BaseStreamSink(not_null_t<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)) {}
explicit BaseStreamSink(not_null_t<stream_ptr_t> stream, not_null_t<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)), mStream(std::move(stream)) {}
void setStream(not_null_t<stream_ptr_t> stream) {
mStream = std::move(stream).release();
}
void handleMessageFormatted(const message_t& /* message */, const char_t* formatted) MIJIN_NOEXCEPT override
{
if (!mStream) {
return;
}
(void) mStream->writeSpan(std::basic_string_view(formatted));
(void) mStream->write('\n');
mStream->flush();
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StreamSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)

View File

@ -0,0 +1,192 @@
#pragma once
#if !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)
#define MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED 1
#include <bit>
#include <cstdint>
#include <utility>
#include "../internal/common.hpp"
#include "../memory/memutil.hpp"
#include "../util/concepts.hpp"
#include "../util/flag.hpp"
namespace mijin
{
MIJIN_DEFINE_FLAG(Owning);
template<typename T, deleter_type<T> TDeleter = AllocatorDeleter<MIJIN_DEFAULT_ALLOCATOR<T>>>
class DynamicPointer
{
public:
using pointer = T*;
using element_type = T;
using deleter_t = TDeleter;
private:
std::uintptr_t mData = 0;
[[no_unique_address]] TDeleter mDeleter;
public:
constexpr DynamicPointer(std::nullptr_t = nullptr) MIJIN_NOEXCEPT {}
DynamicPointer(const DynamicPointer&) = delete;
constexpr DynamicPointer(pointer ptr, Owning owning, TDeleter deleter = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TDeleter>)
: mData(std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0)), mDeleter(std::move(deleter))
{
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_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>))
: DynamicPointer(other.get(), other.isOwning() ? Owning::YES : Owning::NO, TDeleter(std::move(other.mDeleter)))
{
other.mData = 0;
}
constexpr ~DynamicPointer() noexcept
{
reset();
}
DynamicPointer& operator=(const DynamicPointer&) = delete;
DynamicPointer& operator=(std::nullptr_t) MIJIN_NOEXCEPT
{
reset();
return *this;
}
template<typename TOther, typename TOtherDeleter> requires(std::is_assignable_v<TDeleter, TOtherDeleter>)
DynamicPointer& operator=(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TDeleter, TOtherDeleter>))
{
if (this != &other)
{
reset();
mData = std::exchange(other.mData, 0);
mDeleter = std::move(other.mDeleter);
}
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
{
return mData <=> other.mData;
}
constexpr bool operator==(std::nullptr_t) const MIJIN_NOEXCEPT
{
return empty();
}
constexpr bool operator!=(std::nullptr_t) const MIJIN_NOEXCEPT
{
return !empty();
}
constexpr operator bool() const MIJIN_NOEXCEPT
{
return !empty();
}
constexpr bool operator!() const MIJIN_NOEXCEPT
{
return empty();
}
constexpr pointer operator->() const MIJIN_NOEXCEPT
{
return get();
}
constexpr element_type& operator*() const MIJIN_NOEXCEPT
{
return *get();
}
[[nodiscard]]
constexpr bool isOwning() const MIJIN_NOEXCEPT
{
return (mData & 1) == 1;
}
[[nodiscard]]
constexpr bool empty() const MIJIN_NOEXCEPT
{
return mData == 0;
}
[[nodiscard]]
constexpr pointer get() const MIJIN_NOEXCEPT
{
return std::bit_cast<pointer>(mData & ~1);
}
constexpr void reset(pointer ptr, Owning owning) MIJIN_NOEXCEPT
{
if (isOwning())
{
mDeleter(get());
}
mData = std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0);
}
constexpr void reset() MIJIN_NOEXCEPT
{
reset(nullptr, Owning::NO);
}
[[nodiscard]]
pointer release() MIJIN_NOEXCEPT
{
return std::bit_cast<pointer>(std::exchange(mData, 0) & ~1);
}
template<typename TOther, deleter_type<TOther> TOtherDeleter>
friend class DynamicPointer;
};
template<typename T, typename TDeleter>
bool operator==(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
{
return pointer == nullptr;
}
template<typename T, typename TDeleter>
bool operator!=(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
{
return pointer != nullptr;
}
template<typename T, typename... TArgs>
DynamicPointer<T> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
{
return DynamicPointer<T>(new T(std::forward<TArgs>(args)...), Owning::YES);
}
template<typename T, allocator_type_for<T> TAllocator, typename... TArgs>
DynamicPointer<T, AllocatorDeleter<TAllocator>> makeDynamicWithAllocator(TAllocator allocator, TArgs&&... args)
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...> && std::is_nothrow_move_constructible_v<TAllocator>))
{
T* obj = allocator.allocate(1);
if (obj != nullptr)
{
::new(obj) T(std::forward<TArgs>(args)...);
}
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

@ -0,0 +1,90 @@
#pragma once
#if !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)
#define MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED 1
#include <memory>
#include "../internal/common.hpp"
namespace mijin
{
template<typename TAllocator>
class AllocatorDeleter
{
public:
using value_type = std::allocator_traits<TAllocator>::value_type;
using pointer = std::allocator_traits<TAllocator>::pointer;
private:
[[no_unique_address]] TAllocator allocator_;
public:
AllocatorDeleter() = default;
explicit AllocatorDeleter(TAllocator allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator>)
: allocator_(std::move(allocator)) {}
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, const TOtherAllocator&>)
AllocatorDeleter(const AllocatorDeleter<TOtherAllocator>& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator>))
: allocator_(other.allocator_) {}
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, TOtherAllocator&&>)
AllocatorDeleter(AllocatorDeleter<TOtherAllocator>&& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator&&>))
: allocator_(std::move(other.allocator_)) {}
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, const TOtherAllocator&>)
AllocatorDeleter& operator=(const AllocatorDeleter<TOtherAllocator>& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, const TOtherAllocator&>))
{
if (this != static_cast<const void*>(&other))
{
allocator_ = other.allocator_;
}
return *this;
}
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, TOtherAllocator&&>)
AllocatorDeleter& operator=(AllocatorDeleter<TOtherAllocator>&& other)
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, TOtherAllocator&&>))
{
if (this != static_cast<const void*>(&other))
{
allocator_ = std::move(other.allocator_);
}
return *this;
}
void operator()(pointer ptr) MIJIN_NOEXCEPT_IF(noexcept(allocator_.deallocate(ptr, sizeof(value_type))))
{
allocator_.deallocate(ptr, sizeof(value_type));
}
template<typename TOtherAllocator>
friend class AllocatorDeleter;
};
template<typename T>
class AllocatorDeleter<std::allocator<T>>
{
public:
AllocatorDeleter() noexcept = default;
template<typename TOther>
AllocatorDeleter(std::allocator<TOther>) noexcept {}
template<typename TOther>
AllocatorDeleter(const AllocatorDeleter<std::allocator<TOther>>&) noexcept {}
template<typename TOther>
AllocatorDeleter& operator=(const AllocatorDeleter<std::allocator<TOther>>&) noexcept { return *this; }
void operator()(T* ptr) const MIJIN_NOEXCEPT
{
delete ptr;
}
};
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)

View File

@ -0,0 +1,486 @@
#pragma once
#if !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)
#define MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED 1
#include <memory>
#include <utility>
#include "../debug/assert.hpp"
#include "../internal/common.hpp"
#include "../util/align.hpp"
#include "../util/concepts.hpp"
#include "../util/traits.hpp"
#if !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
#if defined(MIJIN_DEBUG)
#define MIJIN_STACK_ALLOCATOR_DEBUG 1
#else
#define MIJIN_STACK_ALLOCATOR_DEBUG 0
#endif
#endif // !defined(MIJIN_STACK_ALLOCATOR_DEBUG)
#if MIJIN_STACK_ALLOCATOR_DEBUG > 1
#include <print>
#include <unordered_map>
#include "../debug/stacktrace.hpp"
#endif
namespace mijin
{
template<typename TValue, typename TStackAllocator>
class StlStackAllocator
{
public:
using value_type = TValue;
private:
TStackAllocator* base_;
public:
explicit StlStackAllocator(TStackAllocator& base) MIJIN_NOEXCEPT : base_(&base) {}
template<typename TOtherValue>
StlStackAllocator(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT : base_(other.base_) {}
template<typename TOtherValue>
StlStackAllocator& operator=(const StlStackAllocator<TOtherValue, TStackAllocator>& other) MIJIN_NOEXCEPT
{
base_ = other.base_;
return *this;
}
auto operator<=>(const StlStackAllocator&) const noexcept = default;
[[nodiscard]]
TValue* allocate(std::size_t count) const
{
void* result = base_->allocate(alignof(TValue), count * sizeof(TValue));
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
++base_->numAllocations_;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
base_->activeAllocations_.emplace(result, captureStacktrace(1));
#endif
return static_cast<TValue*>(result);
}
void deallocate([[maybe_unused]] TValue* ptr, std::size_t /* count */) const MIJIN_NOEXCEPT
{
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
MIJIN_ASSERT(base_->numAllocations_ > 0, "Unbalanced allocations in stack allocators!");
--base_->numAllocations_;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
auto it = base_->activeAllocations_.find(ptr);
if (it != base_->activeAllocations_.end())
{
base_->activeAllocations_.erase(it);
}
else
{
MIJIN_ERROR("Deallocating invalid pointer from StackAllocator.");
}
#endif
}
template<typename TOtherValue, typename TOtherAllocator>
friend class StlStackAllocator;
};
namespace impl
{
struct StackAllocatorSnapshotData
{
struct ChunkSnapshot
{
std::size_t allocated;
};
std::size_t numChunks;
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
std::size_t numAllocations_ = 0;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// just for debugging, so we don't care what memory this uses...
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
#endif
ChunkSnapshot chunks[1]; // may be bigger than that
};
}
class StackAllocatorSnapshot
{
private:
impl::StackAllocatorSnapshotData* data = nullptr;
impl::StackAllocatorSnapshotData* operator->() const MIJIN_NOEXCEPT { return data; }
template<std::size_t chunkSize, template<typename> typename TBacking> requires (allocator_tmpl<TBacking>)
friend class StackAllocator;
};
template<typename TStackAllocator>
class StackAllocatorScope
{
private:
TStackAllocator* allocator_;
StackAllocatorSnapshot snapshot_;
public:
explicit StackAllocatorScope(TStackAllocator& allocator) : allocator_(&allocator), snapshot_(allocator_->createSnapshot()) {}
StackAllocatorScope(const StackAllocatorScope&) = delete;
StackAllocatorScope(StackAllocatorScope&&) = delete;
~StackAllocatorScope() MIJIN_NOEXCEPT
{
allocator_->restoreSnapshot(snapshot_);
}
StackAllocatorScope& operator=(const StackAllocatorScope&) = delete;
StackAllocatorScope& operator=(StackAllocatorScope&&) = delete;
};
template<std::size_t chunkSize = 4096, template<typename> typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl<TBacking>)
class StackAllocator
{
public:
using backing_t = TBacking<void>;
static constexpr std::size_t ACTUAL_CHUNK_SIZE = chunkSize - sizeof(void*) - sizeof(std::size_t);
template<typename T>
using stl_allocator_t = StlStackAllocator<T, StackAllocator<chunkSize, TBacking>>;
private:
struct Chunk
{
std::array<std::byte, ACTUAL_CHUNK_SIZE> data;
Chunk* next;
std::size_t allocated;
};
[[no_unique_address]] TBacking<Chunk> backing_;
Chunk* firstChunk_ = nullptr;
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
std::size_t numAllocations_ = 0;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// just for debugging, so we don't care what memory this uses...
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
#endif
public:
StackAllocator() MIJIN_NOEXCEPT_IF(std::is_nothrow_default_constructible_v<backing_t>) = default;
explicit StackAllocator(backing_t backing) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TBacking<Chunk>, backing_t&&>))
: backing_(std::move(backing)) {}
StackAllocator(const StackAllocator&) = delete;
StackAllocator(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TBacking<Chunk>>)
: firstChunk_(std::exchange(other.firstChunk_, nullptr)) {}
~StackAllocator() noexcept
{
Chunk* chunk = firstChunk_;
while (chunk != nullptr)
{
Chunk* nextChunk = firstChunk_->next;
backing_.deallocate(chunk, 1);
chunk = nextChunk;
}
}
StackAllocator& operator=(const StackAllocator&) = delete;
StackAllocator& operator=(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<TBacking<Chunk>>)
{
if (this != &other)
{
backing_ = std::move(other.backing_);
firstChunk_ = std::exchange(other.firstChunk_, nullptr);
}
return *this;
}
void* allocate(std::size_t alignment, std::size_t size)
{
// first check if this can ever fit
if (size > ACTUAL_CHUNK_SIZE)
{
return nullptr;
}
// then try to find space in the current chunks
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
const std::size_t remaining = ACTUAL_CHUNK_SIZE - chunk->allocated;
if (remaining < size)
{
continue;
}
std::byte* start = &chunk->data[chunk->allocated];
std::byte* pos = mijin::alignUp(start, alignment);
const std::ptrdiff_t alignmentBytes = pos - start;
const std::size_t combinedSize = size + alignmentBytes;
if (remaining < combinedSize)
{
continue;
}
chunk->allocated += combinedSize;
return pos;
}
// no free space in any chunk? allocate a new one
Chunk* newChunk = backing_.allocate(1);
if (newChunk == nullptr)
{
return nullptr;
}
initAndAddChunk(newChunk);
// now try with the new chunk
std::byte* start = newChunk->data.data();
std::byte* pos = mijin::alignUp(start, alignment);
const std::ptrdiff_t alignmentBytes = pos - start;
const std::size_t combinedSize = size + alignmentBytes;
// doesn't fit (due to alignment), time to give up
if (ACTUAL_CHUNK_SIZE < combinedSize)
{
return nullptr;
}
newChunk->allocated = combinedSize;
return pos;
}
void reset() noexcept
{
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
MIJIN_ASSERT(numAllocations_ == 0, "Missing deallocation in StackAllocator!");
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
if (!activeAllocations_.empty())
{
std::println(stderr, "{} active allocations in StackAllocator when resetting!", activeAllocations_.size());
for (const auto& [ptr, stack] : activeAllocations_)
{
if (stack.isError())
{
std::println(stderr, "at {}, no stacktrace ({})", ptr, stack.getError().message);
}
else
{
std::println(stderr, "at 0x{}:\n{}", ptr, stack.getValue());
}
}
MIJIN_TRAP();
}
#endif
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
chunk->allocated = 0;
}
}
bool createChunks(std::size_t count)
{
if (count == 0)
{
return true;
}
Chunk* newChunks = backing_.allocate(count);
if (newChunks == nullptr)
{
return false;
}
// reverse so the chunks are chained from 0 to count (new chunks are inserted in front)
for (std::size_t pos = count; pos > 0; --pos)
{
initAndAddChunk(&newChunks[pos-1]);
}
return true;
}
template<typename T>
stl_allocator_t<T> makeStlAllocator() MIJIN_NOEXCEPT
{
return stl_allocator_t<T>(*this);
}
[[nodiscard]]
std::size_t getNumChunks() const MIJIN_NOEXCEPT
{
std::size_t num = 0;
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
++num;
}
return num;
}
[[nodiscard]]
StackAllocatorSnapshot createSnapshot()
{
if (firstChunk_ == nullptr)
{
return {};
}
using impl::StackAllocatorSnapshotData;
std::size_t numChunks = getNumChunks();
std::size_t snapshotSize = calcSnapshotSize(numChunks);
Chunk* prevFirst = firstChunk_;
StackAllocatorSnapshotData* snapshotData = static_cast<StackAllocatorSnapshotData*>(allocate(alignof(StackAllocatorSnapshotData), snapshotSize));
if (snapshotData == nullptr)
{
// couldn't allocate the snapshot
return {};
}
::new (snapshotData) StackAllocatorSnapshotData;
StackAllocatorSnapshot snapshot;
snapshot.data = snapshotData;
if (firstChunk_ != prevFirst)
{
// new chunk has been added, adjust the snapshot
// the snapshot must be inside the new chunk (but not necessarily at the very beginning, due to alignment)
MIJIN_ASSERT(static_cast<const void*>(snapshot.data) == alignUp(firstChunk_->data.data(), alignof(StackAllocatorSnapshot)), "Snapshot not where it was expected.");
// a chunk might be too small for the snapshot if we grow it (unlikely, but not impossible)
if (ACTUAL_CHUNK_SIZE - firstChunk_->allocated < MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot)) [[unlikely]]
{
firstChunk_->allocated = 0;
return {};
}
// looking good, adjust the numbers
++numChunks;
snapshotSize += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
firstChunk_->allocated += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
}
// now fill out the struct
snapshot->numChunks = numChunks;
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
snapshot->numAllocations_ = numAllocations_;
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// just for debugging, so we don't care what memory this uses...
snapshot->activeAllocations_ = activeAllocations_;
#endif
std::size_t pos = 0;
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next, ++pos)
{
snapshot->chunks[pos].allocated = chunk->allocated;
}
return snapshot;
}
void restoreSnapshot(StackAllocatorSnapshot snapshot) MIJIN_NOEXCEPT
{
if (snapshot.data == nullptr)
{
return;
}
const std::size_t numChunks = getNumChunks();
MIJIN_ASSERT_FATAL(snapshot->numChunks <= numChunks, "Snapshot contains more chunks than the allocator!");
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
MIJIN_ASSERT(snapshot->numAllocations_ >= numAllocations_, "Missing deallocation in StackAllocator!");
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
// TODO: compare and print changes
unsigned numMismatches = 0;
for (const auto& [ptr, stack] : activeAllocations_)
{
if (snapshot->activeAllocations_.contains(ptr))
{
continue;
}
++numMismatches;
if (stack.isError())
{
std::println(stderr, "Missing deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
}
else
{
std::println(stderr, "Missing deallocation at 0x{}:\n{}", ptr, stack.getValue());
}
}
#if 0 // deallocating more than expected shouldn't be a problem
for (const auto& [ptr, stack] : snapshot->activeAllocations_)
{
if (activeAllocations_.contains(ptr))
{
continue;
}
++numMismatches;
if (stack.isError())
{
std::println(stderr, "Unexpected deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
}
else
{
std::println(stderr, "Unexpected deallocation at 0x{}:\n{}", ptr, stack.getValue());
}
}
#endif
if (numMismatches > 0)
{
std::println(stderr, "{} mismatched deallocations when restoring stack allocator snapshot.", numMismatches);
MIJIN_TRAP();
}
#endif
// if we allocated new chunks since the snapshot, these are completely empty now
const std::size_t emptyChunks = numChunks - snapshot->numChunks;
Chunk* chunk = firstChunk_;
for (std::size_t idx = 0; idx < emptyChunks; ++idx)
{
chunk->allocated = 0;
chunk = chunk->next;
}
// the other values are in the snapshot
for (std::size_t idx = 0; idx < snapshot->numChunks; ++idx)
{
chunk->allocated = snapshot->chunks[idx].allocated;
chunk = chunk->next;
}
MIJIN_ASSERT(chunk == nullptr, "Something didn't add up.");
// finally free the space for the snapshot itself
Chunk* snapshotChunk = findChunk(snapshot.data);
MIJIN_ASSERT_FATAL(snapshotChunk != nullptr, "Snapshot not in chunks?");
snapshotChunk->allocated -= calcSnapshotSize(snapshot->numChunks); // note: this might miss the alignment bytes of the snapshot, but that should be fine
snapshot.data->~StackAllocatorSnapshotData();
}
private:
void initAndAddChunk(Chunk* newChunk) noexcept
{
::new (newChunk) Chunk();
// put it in the front
newChunk->next = firstChunk_;
firstChunk_ = newChunk;
}
bool isInChunk(const void* address, const Chunk& chunk) const MIJIN_NOEXCEPT
{
const std::byte* asByte = static_cast<const std::byte*>(address);
return asByte >= chunk.data.data() && asByte < chunk.data.data() + ACTUAL_CHUNK_SIZE;
}
Chunk* findChunk(const void* address) const MIJIN_NOEXCEPT
{
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
{
if (isInChunk(address, *chunk))
{
return chunk;
}
}
return nullptr;
}
static std::size_t calcSnapshotSize(std::size_t numChunks) MIJIN_NOEXCEPT
{
return sizeof(impl::StackAllocatorSnapshotData) + ((numChunks - 1) * MIJIN_STRIDEOF(impl::StackAllocatorSnapshotData::ChunkSnapshot));
}
template<typename TValue, typename TStackAllocator>
friend class StlStackAllocator;
};
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)

View File

@ -0,0 +1,56 @@
#pragma once
#if !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)
#define MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED 1
#include "../internal/common.hpp"
#include "../util/annot.hpp"
namespace mijin
{
template<typename T>
class VirtualAllocator
{
public:
virtual ~VirtualAllocator() noexcept = default;
[[nodiscard]]
virtual owner_t<T*> allocate(std::size_t count) noexcept;
virtual void deallocate(owner_t<T*> ptr, std::size_t count) noexcept;
};
template<typename T, typename TImpl>
class WrappedVirtualAllocator : public VirtualAllocator<T>
{
private:
[[no_unique_address]] TImpl mImpl;
public:
explicit constexpr WrappedVirtualAllocator(TImpl impl = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TImpl>)
: mImpl(std::move(impl)) {}
constexpr WrappedVirtualAllocator(const WrappedVirtualAllocator&) = default;
constexpr WrappedVirtualAllocator(WrappedVirtualAllocator&&) = default;
WrappedVirtualAllocator& operator=(const WrappedVirtualAllocator&) = default;
WrappedVirtualAllocator& operator=(WrappedVirtualAllocator&&) = default;
[[nodiscard]]
owner_t<T*> allocate(std::size_t count) noexcept override
{
return mImpl.allocate(count);
}
void deallocate(owner_t<T*> ptr, std::size_t count) noexcept override
{
mImpl.deallocate(ptr, count);
}
};
template<typename T>
WrappedVirtualAllocator<typename T::value_type, T> makeVirtualAllocator(T allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
{
return WrappedVirtualAllocator<typename T::value_type, T>(std::move(allocator));
}
} // namespace mijin
#endif // !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)

View File

@ -44,7 +44,7 @@ struct HTTPRequestOptions
{
std::string method = "GET";
std::multimap<std::string, std::string> headers;
TypelessBuffer body;
BaseTypelessBuffer<std::allocator> body;
};
struct HTTPResponse
@ -53,7 +53,7 @@ struct HTTPResponse
unsigned status;
std::string statusMessage;
std::multimap<std::string, std::string> headers;
TypelessBuffer body;
BaseTypelessBuffer<std::allocator> body;
};
class HTTPStream

View File

@ -12,7 +12,7 @@
namespace mijin
{
template<typename TChar, typename TTraits = std::char_traits<TChar>, typename TAllocator = std::allocator<TChar>>
template<typename TChar, typename TTraits = std::char_traits<TChar>, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR<TChar>>
class URLBase
{
public:
@ -33,8 +33,8 @@ public:
constexpr URLBase(const URLBase&) = default;
constexpr URLBase(URLBase&&) MIJIN_NOEXCEPT = default;
constexpr URLBase(string_t base) MIJIN_NOEXCEPT : base_(std::move(base)) { parse(); }
constexpr URLBase(string_view_t base) : URLBase(string_t(base.begin(), base.end())) {}
constexpr URLBase(const TChar* base) : URLBase(string_t(base)) {}
constexpr URLBase(string_view_t base, TAllocator allocator = {}) : URLBase(string_t(base.begin(), base.end(), std::move(allocator))) {}
constexpr URLBase(const TChar* base, TAllocator allocator = {}) : URLBase(string_t(base, std::move(allocator))) {}
constexpr URLBase& operator=(const URLBase&) = default;
constexpr URLBase& operator=(URLBase&&) MIJIN_NOEXCEPT = default;

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,6 +4,8 @@
#if !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
#define MIJIN_UTIL_ALIGN_HPP_INCLUDED 1
#include <bit>
#include <cstdint>
#include "../internal/common.hpp"
namespace mijin
@ -18,7 +20,13 @@ constexpr T alignUp(T value, T alignTo) MIJIN_NOEXCEPT
return value;
}
template<typename T>
T* alignUp(T* pointer, std::uintptr_t alignTo) MIJIN_NOEXCEPT
{
return std::bit_cast<T*>(alignUp(std::bit_cast<std::uintptr_t>(pointer), alignTo));
}
#define MIJIN_STRIDEOF(T) mijin::alignUp(sizeof(T), alignof(T))
} // namespace mijin
#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)
#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED)

182
source/mijin/util/annot.hpp Normal file
View File

@ -0,0 +1,182 @@
#pragma once
#if !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)
#define MIJIN_UTIL_ANNOT_HPP_INCLUDED 1
#include <utility>
#include "../internal/common.hpp"
#include "../debug/assert.hpp"
#if !defined(__has_include)
#define __has_include(x) (false)
#endif
#if !defined(MIJIN_USE_GSL)
#if __has_include(<gsl/gsl>)
#define MIJIN_USE_GSL 1
#else
#define MIJIN_USE_GSL 0
#endif
#endif // !defined(MIJIN_USE_GSL)
#include <concepts>
#include "./concepts.hpp"
#if MIJIN_USE_GSL
#include <gsl/gsl>
#endif
namespace mijin
{
template<typename T>
concept nullable_type = !std::is_same_v<T, std::nullptr_t> && requires(T t) { t == nullptr; };
template<nullable_type T>
class NotNullable
{
private:
T base_;
public:
template<typename U> requires(std::is_same_v<T, U> && std::is_copy_constructible_v<T>)
constexpr NotNullable(U base) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
: base_(base)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
// some compilers apparently need this since they are unable to do proper pattern matching ...
NotNullable(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
NotNullable(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
template<typename TOther> requires(std::is_constructible_v<T, const TOther&>)
constexpr NotNullable(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, const TOther&>))
: base_(other.base_)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
template<typename TOther> requires(std::is_constructible_v<T, TOther&&>)
constexpr NotNullable(NotNullable<TOther>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TOther&&>))
: base_(std::exchange(other.base_, nullptr))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
template<typename TArg, typename... TArgs> requires(!std::is_same_v<TArg, std::nullptr_t>
&& (!std::is_same_v<TArg, T> && sizeof...(TArgs) == 0)
&& std::is_constructible_v<T, TArg&&, TArgs&&...>)
constexpr NotNullable(TArg&& arg, TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArg&&, TArgs&&...>))
: base_(std::forward<TArg>(arg), std::forward<TArgs>(args)...)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(T&& base) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
requires(std::is_move_constructible_v<T>)
: base_(std::move(base))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
requires(std::is_copy_constructible_v<T>)
: base_(other.base_)
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
requires(std::is_move_constructible_v<T>)
: base_(std::exchange(other.base_, nullptr))
{
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
}
constexpr NotNullable(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
// some compilers apparently need this since they are unable to do proper pattern matching ...
NotNullable& operator=(const NotNullable&) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>) = default;
NotNullable& operator=(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>) = default;
constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_assignable_v<T>)
requires(std::is_copy_assignable_v<T>)
{
if (this != &other)
{
this->base_ = other.base_;
}
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
return *this;
}
constexpr NotNullable& operator=(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<T>)
requires(std::is_move_assignable_v<T>)
{
if (this != &other)
{
this->base_ = std::exchange(other.base_, nullptr);
}
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
return *this;
}
constexpr NotNullable& operator=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
template<std::equality_comparable_with<T> TOther>
bool operator==(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
{
return base_ == other.base_;
}
template<std::equality_comparable_with<T> TOther>
bool operator!=(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
{
return base_ != other.base_;
}
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
{
return base_ == other;
}
template<nullable_type TOther> requires(std::equality_comparable_with<T, TOther> && !std::is_same_v<TOther, std::nullptr_t>)
bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
{
return base_ != other;
}
bool operator==(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
bool operator!=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
constexpr operator const T&() const MIJIN_NOEXCEPT { return get(); }
constexpr operator std::nullptr_t() const MIJIN_DELETE("Type is not nullable.");
constexpr const T& operator->() const MIJIN_NOEXCEPT { return get(); }
constexpr decltype(auto) operator*() const MIJIN_NOEXCEPT_IF(noexcept(*get())) { return *get(); }
NotNullable& operator++() MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable& operator--() MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable operator++(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable operator--(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable& operator+=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
NotNullable& operator-=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
void operator[](std::ptrdiff_t) const MIJIN_DELETE("Operator disabled for non-nullable types.");
[[nodiscard]]
constexpr const T& get() const MIJIN_NOEXCEPT { return base_; }
[[nodiscard]]
constexpr T release() && MIJIN_NOEXCEPT { return std::exchange(base_, nullptr); }
template<nullable_type TOther>
friend class NotNullable;
};
#if MIJIN_USE_GSL
template<mijin::pointer_type T>
using owner_t = gsl::owner<T>;
#else
template<mijin::pointer_type T>
using owner_t = T;
#endif
template<typename T>
using not_null_t = NotNullable<T>;
}
#endif // !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)

View File

@ -0,0 +1,55 @@
#pragma once
#if !defined(MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED)
#define MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED 1
#include "../internal/common.hpp"
namespace mijin
{
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseAnsiFontEffects
{
using char_t = TChar;
static constexpr const char_t* RESET = MIJIN_SMART_QUOTE(char_t, "0");
static constexpr const char_t* FG_BLACK = MIJIN_SMART_QUOTE(char_t, "30");
static constexpr const char_t* FG_RED = MIJIN_SMART_QUOTE(char_t, "31");
static constexpr const char_t* FG_GREEN = MIJIN_SMART_QUOTE(char_t, "32");
static constexpr const char_t* FG_YELLOW = MIJIN_SMART_QUOTE(char_t, "33");
static constexpr const char_t* FG_BLUE = MIJIN_SMART_QUOTE(char_t, "34");
static constexpr const char_t* FG_MAGENTA = MIJIN_SMART_QUOTE(char_t, "35");
static constexpr const char_t* FG_CYAN = MIJIN_SMART_QUOTE(char_t, "36");
static constexpr const char_t* FG_WHITE = MIJIN_SMART_QUOTE(char_t, "37");
static constexpr const char_t* FG_BRIGHT_BLACK = MIJIN_SMART_QUOTE(char_t, "90");
static constexpr const char_t* FG_BRIGHT_RED = MIJIN_SMART_QUOTE(char_t, "91");
static constexpr const char_t* FG_BRIGHT_GREEN = MIJIN_SMART_QUOTE(char_t, "92");
static constexpr const char_t* FG_BRIGHT_YELLOW = MIJIN_SMART_QUOTE(char_t, "93");
static constexpr const char_t* FG_BRIGHT_BLUE = MIJIN_SMART_QUOTE(char_t, "94");
static constexpr const char_t* FG_BRIGHT_MAGENTA = MIJIN_SMART_QUOTE(char_t, "95");
static constexpr const char_t* FG_BRIGHT_CYAN = MIJIN_SMART_QUOTE(char_t, "96");
static constexpr const char_t* FG_BRIGHT_WHITE = MIJIN_SMART_QUOTE(char_t, "97");
static constexpr const char_t* BG_BLACK = MIJIN_SMART_QUOTE(char_t, "40");
static constexpr const char_t* BG_RED = MIJIN_SMART_QUOTE(char_t, "41");
static constexpr const char_t* BG_GREEN = MIJIN_SMART_QUOTE(char_t, "42");
static constexpr const char_t* BG_YELLOW = MIJIN_SMART_QUOTE(char_t, "43");
static constexpr const char_t* BG_BLUE = MIJIN_SMART_QUOTE(char_t, "44");
static constexpr const char_t* BG_MAGENTA = MIJIN_SMART_QUOTE(char_t, "45");
static constexpr const char_t* BG_CYAN = MIJIN_SMART_QUOTE(char_t, "46");
static constexpr const char_t* BG_WHITE = MIJIN_SMART_QUOTE(char_t, "47");
static constexpr const char_t* BG_BRIGHT_BLACK = MIJIN_SMART_QUOTE(char_t, "100");
static constexpr const char_t* BG_BRIGHT_RED = MIJIN_SMART_QUOTE(char_t, "101");
static constexpr const char_t* BG_BRIGHT_GREEN = MIJIN_SMART_QUOTE(char_t, "102");
static constexpr const char_t* BG_BRIGHT_YELLOW = MIJIN_SMART_QUOTE(char_t, "103");
static constexpr const char_t* BG_BRIGHT_BLUE = MIJIN_SMART_QUOTE(char_t, "104");
static constexpr const char_t* BG_BRIGHT_MAGENTA = MIJIN_SMART_QUOTE(char_t, "105");
static constexpr const char_t* BG_BRIGHT_CYAN = MIJIN_SMART_QUOTE(char_t, "106");
static constexpr const char_t* BG_BRIGHT_WHITE = MIJIN_SMART_QUOTE(char_t, "107");
};
MIJIN_DEFINE_CHAR_VERSIONS(AnsiFontEffects)
}
#endif // !defined(MIJIN_UTIL_ANSI_COLORS_HPP_INCLUDED)

View File

@ -8,6 +8,8 @@
#include <atomic>
#include <cstddef>
#include "../debug/assert.hpp"
namespace mijin
{

View File

@ -29,6 +29,7 @@ template<typename TBits>
struct BitFlags
{
using bits_t = TBits;
static constexpr bool ENABLE_TO_INT = false;
constexpr TBits& operator |=(const BitFlags& other) {
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
@ -103,6 +104,11 @@ concept BitFlagsType = is_bitflags_v<T>;
template<std::integral TInt, BitFlagsType T>
TInt bitFlagsToInt(const BitFlags<T>& flags) noexcept
{
// If a BitFlags object contains padding (number of defined bits < number of bits in type), the bits in the padding might not be 0.
// In that case bit_casting will produce an incorrect value.
// Filling the gaps with padding bits fixes this, but is unfortunately quite error prone :/.
static_assert(T::ENABLE_TO_INT, "bitFlagsToInt not enabled for this type. Make sure the number of bits defined is the same as the number of bits in the type and define ENABLE_TO_INT to true.");
static constexpr std::size_t BYTES = std::min(sizeof(T), sizeof(TInt));
TInt result = 0;

View File

@ -5,6 +5,7 @@
#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1
#include <type_traits>
#include "./traits.hpp"
namespace mijin
{
@ -39,6 +40,32 @@ concept pointer_type = std::is_pointer_v<T>;
template<typename T>
concept reference_type = std::is_reference_v<T>;
namespace impl
{
template<typename T>
using pointer_t = typename T::pointer;
}
template<typename T>
concept allocator_type = requires(T alloc, typename T::value_type value, detect_or_t<typename T::value_type*, impl::pointer_t, T> pointer, int count)
{
typename T::value_type;
{ alloc.allocate(count) } -> std::same_as<decltype(pointer)>;
{ alloc.deallocate(pointer, count) } -> std::same_as<void>;
} && !std::is_const_v<typename T::value_type> && !std::is_volatile_v<typename T::value_type>;
template<typename T, typename TOther>
concept allocator_type_for = allocator_type<T> && std::is_same_v<typename T::value_type, TOther>;
template<template<typename> typename T>
concept allocator_tmpl = allocator_type<T<int>>;
template<typename T, typename TData>
concept deleter_type = requires(T deleter, TData* ptr)
{
deleter(ptr);
};
//
// public functions
//

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

@ -6,6 +6,8 @@
#define MIJIN_UTIL_HASH_HPP_INCLUDED 1
#include <functional>
#include <tuple>
#include <utility>
namespace mijin
{
@ -16,4 +18,21 @@ inline void hashCombine(std::size_t& seed, const T& value, const THasher& hasher
}
}
template<typename... T>
struct std::hash<std::tuple<T...>>
{
std::size_t operator()(const std::tuple<T...>& tuple) const noexcept
{
return hashImpl(tuple, std::index_sequence_for<T...>());
}
template<std::size_t... indices>
std::size_t hashImpl(const std::tuple<T...>& tuple, std::index_sequence<indices...>) const noexcept
{
std::size_t result = 0;
(mijin::hashCombine(result, std::get<indices>(tuple)), ...);
return result;
}
};
#endif // MIJIN_UTIL_HASH_HPP_INCLUDED

View File

@ -0,0 +1,42 @@
#pragma once
#if !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)
#define MIJIN_UTIL_MISC_HPP_INCLUDED 1
#include <array>
#include <utility>
namespace mijin
{
//
// public functions
//
template<auto V, typename T>
constexpr decltype(auto) idValue(T&& value)
{
return std::forward<T>(value);
}
namespace impl
{
template<typename T, typename... TArgs>
struct ConstructArrayHelper
{
template<std::size_t... I>
static constexpr std::array<T, sizeof...(I)> construct(const TArgs&... args, std::index_sequence<I...>)
{
return {idValue<I>(T(args...))...};
}
};
}
template<typename T, std::size_t count, typename... TArgs>
constexpr std::array<T, count> constructArray(const TArgs&... args)
{
return impl::ConstructArrayHelper<T, TArgs...>::construct(args..., std::make_index_sequence<count>());
}
}
#endif // !defined(MIJIN_UTIL_MISC_HPP_INCLUDED)

View File

@ -5,11 +5,14 @@
#include "../debug/assert.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
#include <bit>
#include <cstring>
#include <mutex>
#include <dlfcn.h>
#include <pthread.h>
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#include <array>
#include <malloc.h>
#include <windows.h>
#include "../util/winundef.hpp"
#endif
@ -139,4 +142,40 @@ std::string getExecutablePath() MIJIN_NOEXCEPT
#endif
}
void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
{
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
return _aligned_malloc(size, alignment);
#else
return std::aligned_alloc(alignment, size);
#endif
}
void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
{
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
return _aligned_realloc(ptr, size, alignment);
#else
void* newPtr = std::realloc(ptr, size);
if (newPtr == ptr || (std::bit_cast<std::uintptr_t>(newPtr) % alignment) == 0)
{
return newPtr;
}
// bad luck, have to copy a second time
void* newPtr2 = std::aligned_alloc(alignment, size);
std::memcpy(newPtr2, newPtr, size);
std::free(newPtr);
return newPtr2;
#endif
}
void alignedFree(void* ptr)
{
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
_aligned_free(ptr);
#else
std::free(ptr);
#endif
}
} // namespace mijin

View File

@ -81,6 +81,10 @@ void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT;
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) MIJIN_NOEXCEPT;
[[nodiscard]] void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
[[nodiscard]] void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
void alignedFree(void* ptr);
SharedLibrary::~SharedLibrary() MIJIN_NOEXCEPT
{
close();

View File

@ -7,6 +7,9 @@
#include <algorithm>
#include <array>
#include <charconv>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <limits>
#include <locale>
@ -17,6 +20,7 @@
#include "./iterators.hpp"
#include "../internal/common.hpp"
#include "../util/traits.hpp"
namespace mijin
{
@ -29,12 +33,70 @@ namespace mijin
// public constants
//
namespace detail
{
template<typename TChar>
static constexpr std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
}
template<typename TChar>
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
= {detail::DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), detail::DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
//
// public traits
//
template<typename TString>
using char_type_t = decltype(std::string_view(std::declval<TString>()))::value_type;
template<typename T>
inline constexpr bool is_string_v = is_template_instance_v<std::basic_string, std::remove_cvref_t<T>>;
template<typename T>
concept std_string_type = is_string_v<T>;
template<typename T>
inline constexpr bool is_string_view_v = is_template_instance_v<std::basic_string_view, std::remove_cvref_t<T>>;
template<typename T>
concept std_string_view_type = is_string_view_v<T>;
template<typename T>
inline constexpr bool is_char_v = is_any_type_v<std::remove_cvref_t<T>, char, wchar_t, char8_t, char16_t, char32_t>;
template<typename T>
concept char_type = is_char_v<T>;
template<typename T>
inline constexpr bool is_cstring_v = std::is_pointer_v<T> && is_char_v<std::remove_pointer_t<T>>;
template<typename T>
concept cstring_type = is_cstring_v<T>;
template<typename T>
struct str_char_type
{
using type = void;
};
template<char_type T>
struct str_char_type<T*>
{
using type = std::remove_cvref_t<T>;
};
template<std_string_view_type T>
struct str_char_type<T>
{
using type = typename std::remove_cvref_t<T>::value_type;
};
template<std_string_type T>
struct str_char_type<T>
{
using type = typename std::remove_cvref_t<T>::value_type;
};
template<typename T>
using str_char_type_t = str_char_type<T>::type;
//
// public types
@ -46,6 +108,361 @@ struct SplitOptions
bool ignoreEmpty = true;
};
struct [[nodiscard]] ConvertCharTypeResult
{
unsigned numRead = 0;
unsigned numWritten = 0;
constexpr operator bool() const MIJIN_NOEXCEPT
{
return numRead != 0 || numWritten != 0;
}
constexpr bool operator !() const MIJIN_NOEXCEPT
{
return !static_cast<bool>(*this);
}
};
struct SplitViewOptions
{
bool ignoreEmpty = true;
bool trim = false;
};
template<typename TChar, TChar splitAt, SplitViewOptions options = SplitViewOptions(), typename TCharTraits = std::char_traits<TChar>>
struct SplitStringTraitsCT
{
using char_t = TChar;
using string_view_t = std::basic_string_view<TChar, TCharTraits>;
static constexpr char_t getSplitAt() MIJIN_NOEXCEPT { return splitAt; }
static constexpr bool getIgnoreEmpty() MIJIN_NOEXCEPT { return options.ignoreEmpty; }
static constexpr bool getTrim() MIJIN_NOEXCEPT { return options.trim; }
static constexpr auto getTrimChars() MIJIN_NOEXCEPT { return DEFAULT_TRIM_CHARS<char_t>; }
};
template<typename TChar, typename TCharTraits = std::char_traits<TChar>>
struct SplitStringTraitsRT
{
using char_t = TChar;
using string_view_t = std::basic_string_view<TChar, TCharTraits>;
char_t splitAt;
bool ignoreEmpty;
string_view_t trimChars = {};
constexpr char_t getSplitAt() const MIJIN_NOEXCEPT { return splitAt; }
constexpr bool getIgnoreEmpty() const MIJIN_NOEXCEPT { return ignoreEmpty; }
constexpr bool getTrim() const MIJIN_NOEXCEPT { return !trimChars.empty(); }
constexpr string_view_t getTrimChars() const MIJIN_NOEXCEPT { return trimChars; }
};
template<typename T, typename TChar>
concept SplitStringTraitsType = std::is_copy_constructible_v<T> && requires(const T& object)
{
typename T::char_t;
typename T::string_view_t;
{ object.getSplitAt() } -> std::convertible_to<TChar>;
{ object.getIgnoreEmpty() } -> std::convertible_to<bool>;
{ object.getTrim() } -> std::convertible_to<bool>;
{ object.getTrimChars() } -> std::convertible_to<typename T::string_view_t>;
};
static_assert(SplitStringTraitsType<SplitStringTraitsCT<char, ' '>, char>);
static_assert(SplitStringTraitsType<SplitStringTraitsRT<char>, char>);
template<typename TChar, typename TLine, SplitViewOptions options = SplitViewOptions(), typename TCharTraits = std::char_traits<TChar>>
struct SplitLineTraitsCT : SplitStringTraitsCT<TChar, '\n', options, TCharTraits>
{
using base_t = SplitStringTraitsCT<TChar, '\n', options, TCharTraits>;
using char_t = TChar;
using line_t = TLine;
line_t line = 1;
using base_t::getSplitAt;
using base_t::getIgnoreEmpty;
using base_t::getTrim;
using base_t::getTrimChars;
constexpr void onNext() MIJIN_NOEXCEPT {
++line;
}
constexpr line_t getLine() const MIJIN_NOEXCEPT { return line; }
};
template<typename TChar, typename TLine, typename TCharTraits = std::char_traits<TChar>>
struct SplitLineTraitsRT
{
using char_t = TChar;
using line_t = TLine;
using string_view_t = std::basic_string_view<TChar, TCharTraits>;
line_t line = 1;
bool ignoreEmpty;
string_view_t trimChars = {};
constexpr char_t getSplitAt() const MIJIN_NOEXCEPT { return '\n'; }
constexpr bool getIgnoreEmpty() const MIJIN_NOEXCEPT { return ignoreEmpty; }
constexpr bool getTrim() const MIJIN_NOEXCEPT { return !trimChars.empty(); }
constexpr string_view_t getTrimChars() const MIJIN_NOEXCEPT { return trimChars; }
constexpr line_t getLine() const MIJIN_NOEXCEPT { return line; }
constexpr void onNext() MIJIN_NOEXCEPT {
++line;
}
};
template<typename T, typename TChar, typename TLine>
concept SplitLineTraitsType = SplitStringTraitsType<T, TChar> && requires (const T& object)
{
{ object.getLine() } -> std::convertible_to<TLine>;
};
static_assert(SplitLineTraitsType<SplitLineTraitsCT<char, unsigned>, char, unsigned>);
static_assert(SplitLineTraitsType<SplitLineTraitsRT<char, unsigned>, char, unsigned>);
template<typename TString, typename TChars>
[[nodiscard]]
auto trim(TString&& string, TChars&& chars);
template<typename TChar, SplitStringTraitsType<TChar> TTraits>
class SplitStringIterator
{
public:
using char_t = TChar;
using traits_t = TTraits;
using string_view_t = traits_t::string_view_t;
using base_t = string_view_t::iterator;
using value_type = string_view_t;
private:
[[no_unique_address]] traits_t traits_;
string_view_t full_;
string_view_t::iterator pos_;
string_view_t::iterator next_;
public:
constexpr SplitStringIterator(string_view_t full, base_t pos, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: full_(full), pos_(pos), traits_(std::move(traits))
{
findNext();
}
constexpr explicit SplitStringIterator(traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: traits_(std::move(traits)) {}
constexpr SplitStringIterator(const SplitStringIterator&) noexcept(std::is_nothrow_copy_constructible_v<traits_t>) = default;
constexpr SplitStringIterator(SplitStringIterator&&) noexcept(std::is_nothrow_move_constructible_v<traits_t>) = default;
constexpr SplitStringIterator& operator=(const SplitStringIterator&) noexcept(std::is_nothrow_copy_assignable_v<traits_t>) = default;
constexpr SplitStringIterator& operator=(SplitStringIterator&&) noexcept(std::is_nothrow_move_assignable_v<traits_t>) = default;
constexpr bool operator==(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ == other.pos_; }
constexpr bool operator!=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ != other.pos_; }
constexpr bool operator<(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ < other.pos_; }
constexpr bool operator<=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ <= other.pos_; }
constexpr bool operator>(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ > other.pos_; }
constexpr bool operator>=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ >= other.pos_; }
[[nodiscard]]
traits_t& getTraits() MIJIN_NOEXCEPT
{
return traits_;
}
[[nodiscard]]
const traits_t& getTraits() const MIJIN_NOEXCEPT
{
return traits_;
}
constexpr value_type operator*() const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(pos_ != full_.end(), "Dereferencing an invalid iterator.");
string_view_t result{pos_, next_};
if (traits_.getTrim())
{
result = trim(result, traits_.getTrimChars());
}
return result;
}
constexpr SplitStringIterator& operator++() MIJIN_NOEXCEPT
{
MIJIN_ASSERT(pos_ != full_.end(), "Iterating past end.");
if (next_ == full_.end()) {
pos_ = full_.end();
}
else
{
pos_ = std::next(next_);
findNext();
}
return *this;
}
constexpr SplitStringIterator operator++(int) const MIJIN_NOEXCEPT
{
SplitStringIterator copy(*this);
++copy;
return copy;
}
// TODO
// SplitStringIterator& operator--() MIJIN_NOEXCEPT
// {
// MIJIN_ASSERT(pos_ != full_.begin(), "Iterating past begin.");
// next_ = std::prev(pos_);
// pos_ = std::find(std::reverse_iterator(next_), std::reverse_iterator(full_.begin()), separator).base();
// }
private:
constexpr void findNext()
{
while (true)
{
if constexpr (requires{{ traits_.onNext() };}) {
traits_.onNext();
}
next_ = std::find(pos_, full_.end(), traits_.getSplitAt());
if (!traits_.getIgnoreEmpty() || pos_ == full_.end()) {
break;
}
if (traits_.getTrim())
{
const string_view_t trimChars = traits_.getTrimChars();
typename string_view_t::iterator trimmedPos = std::find_if(pos_, next_, [&](char_t chr)
{
return !trimChars.contains(chr);
});
if (trimmedPos == next_)
{
pos_ = next_; // skip this part
}
}
if (pos_ != next_) {
break;
}
pos_ = std::next(pos_);
}
}
};
template<typename TChar, SplitStringTraitsType<TChar> TTraits>
class SplitStringRange
{
public:
using char_t = TChar;
using traits_t = TTraits;
using string_view_t = traits_t::string_view_t;
using iterator = SplitStringIterator<char_t, traits_t>;
private:
[[no_unique_address]] traits_t traits_;
string_view_t stringView_;
public:
constexpr explicit SplitStringRange(string_view_t stringView, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: stringView_(stringView), traits_(std::move(traits)) {}
constexpr SplitStringRange(const SplitStringRange&) noexcept(std::is_nothrow_copy_constructible_v<traits_t>) = default;
constexpr SplitStringRange(SplitStringRange&&) noexcept(std::is_nothrow_move_constructible_v<traits_t>) = default;
constexpr SplitStringRange& operator=(const SplitStringRange&) noexcept(std::is_nothrow_copy_assignable_v<traits_t>) = default;
constexpr SplitStringRange& operator=(SplitStringRange&&) noexcept(std::is_nothrow_move_assignable_v<traits_t>) = default;
constexpr auto operator<=>(const SplitStringRange&) const noexcept = default;
constexpr iterator begin() const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<traits_t>)
{
return iterator(stringView_, stringView_.begin(), traits_);
}
constexpr iterator end() const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<traits_t>)
{
return iterator(stringView_, stringView_.end(), traits_);
}
};
template<typename TChar, typename TLine = unsigned, SplitLineTraitsType<TChar, TLine> TTraits = SplitLineTraitsCT<TChar, TLine>>
class LineIterator
{
public:
using char_t = TChar;
using line_t = TLine;
using traits_t = TTraits;
using base_t = SplitStringIterator<TChar, traits_t>;
using string_view_t = base_t::string_view_t;
using value_type = std::pair<string_view_t, line_t>;
private:
base_t base_ = {};
public:
constexpr LineIterator(string_view_t full, string_view_t::iterator pos, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: base_(full, pos, std::move(traits)) {}
constexpr explicit LineIterator(traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: base_(std::move(traits)) {}
LineIterator(const LineIterator&) noexcept = default;
LineIterator(LineIterator&&) noexcept = default;
LineIterator& operator=(const LineIterator&) noexcept = default;
LineIterator& operator=(LineIterator&&) noexcept = default;
bool operator==(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ == other.base_; }
bool operator!=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ != other.base_; }
bool operator<(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ < other.base_; }
bool operator>(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ > other.base_; }
bool operator<=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ <= other.base_; }
bool operator>=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ >= other.base_; }
constexpr value_type operator*() const MIJIN_NOEXCEPT
{
string_view_t stringView = *base_;
if (!base_.getTraits().getTrim())
{
// always split \r, even if not trimming other whitespace
if (stringView.ends_with('\r')) {
stringView = stringView.substr(0, stringView.size() - 1);
}
}
return {stringView, base_.getTraits().line};
}
constexpr LineIterator& operator++() MIJIN_NOEXCEPT
{
++base_;
return *this;
}
constexpr LineIterator operator++(int) const MIJIN_NOEXCEPT
{
SplitStringIterator copy(*this);
++copy;
return copy;
}
};
template<typename TChar, typename TLine = unsigned, SplitLineTraitsType<TChar, TLine> TTraits = SplitLineTraitsCT<TChar, TLine>>
class LineRange
{
public:
using char_t = TChar;
using line_t = TLine;
using traits_t = TTraits;
using iterator = LineIterator<char_t, line_t, traits_t>;
using string_view_t = iterator::string_view_t;
private:
[[no_unique_address]] traits_t traits_;
string_view_t stringView_;
public:
constexpr explicit LineRange(string_view_t stringView, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: traits_(std::move(traits)), stringView_(stringView) {}
constexpr LineRange(const LineRange&) noexcept(std::is_nothrow_copy_constructible_v<traits_t>) = default;
constexpr LineRange(LineRange&&) noexcept(std::is_nothrow_move_constructible_v<traits_t>) = default;
constexpr LineRange& operator=(const LineRange&) noexcept(std::is_nothrow_copy_assignable_v<traits_t>) = default;
constexpr LineRange& operator=(LineRange&&) noexcept(std::is_nothrow_move_assignable_v<traits_t>) = default;
constexpr auto operator<=>(const LineRange&) const noexcept = default;
constexpr iterator begin() const MIJIN_NOEXCEPT
{
return iterator(stringView_, stringView_.begin(), traits_);
}
constexpr iterator end() const MIJIN_NOEXCEPT
{
return iterator(stringView_, stringView_.end(), traits_);
}
};
//
// public functions
//
@ -221,13 +638,6 @@ std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TT
{
return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim);
}
template<typename TChar>
static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
template<typename TChar>
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
= {DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
}
template<typename TLeft, typename TRight>
@ -244,6 +654,61 @@ template<std::size_t count, typename TLeft, typename TRight>
std::basic_string_view(std::forward<TRight>(separator)), options, outNumResults);
}
template<typename TTraits, typename TChar> requires (SplitStringTraitsType<TTraits, TChar>)
[[nodiscard]] SplitStringRange<TChar, TTraits> splitView(typename TTraits::string_view_t stringView, TTraits traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TTraits>)
{
return SplitStringRange<TChar, TTraits>(stringView, std::move(traits));
}
template<auto splitAt, SplitViewOptions options = SplitViewOptions(), typename TStringView, typename TChar = decltype(splitAt), typename TCharTraits = std::char_traits<TChar>>
[[nodiscard]] SplitStringRange<TChar, SplitStringTraitsCT<TChar, splitAt, options, TCharTraits>> splitView(TStringView&& stringView) MIJIN_NOEXCEPT
{
return splitView<SplitStringTraitsCT<TChar, splitAt, options, TCharTraits>>(std::basic_string_view<TChar, TCharTraits>(std::forward<TStringView>(stringView)));
}
template<typename TStringView, typename TChar, typename TCharTraits = std::char_traits<TChar>, typename TTrimChars = std::basic_string_view<TChar, TCharTraits>>
[[nodiscard]] auto splitView(TStringView&& stringView, TChar splitAt, bool ignoreEmpty = true, TTrimChars trimChars = {}) MIJIN_NOEXCEPT
{
return splitView(std::basic_string_view<TChar, TCharTraits>(std::forward<TStringView>(stringView)), SplitStringTraitsRT<TChar, TCharTraits>{
.splitAt = splitAt,
.ignoreEmpty = ignoreEmpty,
.trimChars = std::basic_string_view<TChar, TCharTraits>(std::forward<TTrimChars>(trimChars))
});
}
template<typename TTraits, typename TChar, typename TLine = unsigned> requires(SplitLineTraitsType<TTraits, TChar, TLine>)
[[nodiscard]] LineRange<TChar, TLine, TTraits> splitLines(typename TTraits::string_view_t stringView, TTraits traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TTraits>)
{
return LineRange<TChar, TLine, TTraits>(stringView, std::move(traits));
}
template<typename TLine = unsigned, SplitViewOptions options = SplitViewOptions{.ignoreEmpty=false},
typename TParam,
typename TStringView = decltype(std::basic_string_view(std::declval<TParam&&>())),
typename TChar = typename TStringView::value_type,
typename TCharTraits = typename TStringView::traits_type,
typename TTraits = SplitLineTraitsCT<TChar, TLine, options, TCharTraits>>
[[nodiscard]]
auto splitLines(TParam&& stringView) MIJIN_NOEXCEPT -> LineRange<TChar, TLine, TTraits>
{
return LineRange<TChar, TLine, TTraits>(std::basic_string_view(std::forward<TParam>(stringView)));
}
template<typename TParam, typename TLine = unsigned,
typename TStringView = decltype(std::basic_string_view(std::declval<TParam&&>())),
typename TChar = typename TStringView::value_type,
typename TCharTraits = typename TStringView::traits_type,
typename TTraits = SplitLineTraitsRT<TChar, TLine, TCharTraits>,
typename TTrimChars = TStringView>
[[nodiscard]]
auto splitLines(TParam&& stringView, bool ignoreEmpty, TTrimChars&& trimChars = {}) MIJIN_NOEXCEPT -> LineRange<TChar, TLine, TTraits>
{
return LineRange<TChar, TLine, TTraits>(std::basic_string_view(std::forward<TParam>(stringView)), TTraits{
.ignoreEmpty = ignoreEmpty,
.trimChars = std::basic_string_view<TChar, TCharTraits>(std::forward<TTrimChars>(trimChars))
});
}
template<typename TString, typename TChars>
[[nodiscard]]
auto trimPrefix(TString&& string, TChars&& chars)
@ -255,7 +720,7 @@ template<typename TString>
[[nodiscard]]
auto trimPrefix(TString&& string)
{
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
return trimPrefix(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
}
template<typename TString, typename TChars>
@ -269,7 +734,7 @@ template<typename TString>
[[nodiscard]]
auto trimSuffix(TString&& string)
{
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
return trimSuffix(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
}
template<typename TString, typename TChars>
@ -283,7 +748,7 @@ template<typename TString>
[[nodiscard]]
auto trim(TString&& string)
{
return trim(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
return trim(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
}
template<typename TLeft, typename TRight>
@ -387,13 +852,45 @@ constexpr bool isHexadecimalChar(TChar chr) noexcept
|| (chr >= TChar('a') && chr <= TChar('f'));
}
template<typename TChar>
bool compareIgnoreCase(TChar left, TChar right) MIJIN_NOEXCEPT
{
return std::tolower(left) == std::tolower(right);
}
[[nodiscard]]
inline auto findIgnoreCase(std::string_view haystack, std::string_view needle)
{
return std::ranges::search(haystack, needle, &compareIgnoreCase<char>);
}
[[nodiscard]]
inline bool startsWithIgnoreCase(std::string_view string, std::string_view part)
{
if (part.size() > string.size())
{
return false;
}
return std::ranges::equal(string.substr(0, part.size()), part, &compareIgnoreCase<char>);
}
[[nodiscard]]
inline bool endsWithIgnoreCase(std::string_view string, std::string_view part)
{
if (part.size() > string.size())
{
return false;
}
return std::ranges::equal(string.substr(string.size() - part.size()), part, &compareIgnoreCase<char>);
}
namespace pipe
{
struct Join
{
const char* delimiter;
explicit Join(const char* delimiter_) MIJIN_NOEXCEPT : delimiter(delimiter_) {}
explicit Join(const char* delimiter_) MIJIN_NOEXCEPT: delimiter(delimiter_) {}
};
template<typename TIterable>
@ -401,6 +898,175 @@ auto operator|(TIterable&& iterable, const Join& joiner)
{
return join(std::forward<TIterable>(iterable), joiner.delimiter);
}
} // namespace pipe
template<typename TFrom, typename TTo>
ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo, std::mbstate_t& mbstate) MIJIN_NOEXCEPT
{
if constexpr (std::is_same_v<TFrom, char>)
{
if constexpr (std::is_same_v<TTo, wchar_t>)
{
const std::size_t result = std::mbrtowc(outTo, chrFrom, numFrom, &mbstate);
if (result == static_cast<std::size_t>(-1))
{
return {};
}
return {
.numRead = static_cast<unsigned>(result),
.numWritten = 1
};
}
if constexpr (std::is_same_v<TTo, char8_t>)
{
}
}
if constexpr (std::is_same_v<TFrom, wchar_t>)
{
if constexpr (std::is_same_v<TTo, char>)
{
if (numTo < MB_CUR_MAX)
{
char tmpBuf[MB_LEN_MAX];
const ConvertCharTypeResult result = convertCharType(chrFrom, numFrom, tmpBuf, MB_LEN_MAX, mbstate);
if (result && result.numWritten <= numTo)
{
std::memcpy(outTo, tmpBuf, result.numWritten);
}
return result;
}
const std::size_t result = std::wcrtomb(outTo, *chrFrom, &mbstate);
if (result == static_cast<std::size_t>(-1))
{
return {};
}
return {
.numRead = 1,
.numWritten = static_cast<unsigned>(result)
};
}
if constexpr (std::is_same_v<TTo, char8_t>)
{
}
}
if constexpr (std::is_same_v<TFrom, char8_t>)
{
}
}
template<typename TFrom, typename TTo>
ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo) MIJIN_NOEXCEPT
{
std::mbstate_t mbstate;
return convertCharType(chrFrom, numFrom, outTo, numTo, mbstate);
}
template<typename TIterator>
struct [[nodiscard]] ConvertStringTypeResult
{
TIterator iterator;
bool success;
};
template<typename TTo, typename TFrom, typename TFromTraits, std::output_iterator<TTo> TIterator>
ConvertStringTypeResult<TIterator> convertStringType(std::basic_string_view<TFrom, TFromTraits> strFrom, TIterator outIterator)
{
TTo outBuffer[MB_LEN_MAX];
std::mbstate_t mbstate = {};
for (auto it = strFrom.begin(); it != strFrom.end();)
{
const std::size_t remaining = std::distance(it, strFrom.end());
const ConvertCharTypeResult result = convertCharType(&*it, remaining, outBuffer, MB_LEN_MAX, mbstate);
if (!result)
{
return {
.iterator = outIterator,
.success = false
};
}
for (unsigned pos = 0; pos < result.numWritten; ++pos)
{
*outIterator = outBuffer[pos];
++outIterator;
}
it = std::next(it, result.numRead);
}
return {
.iterator = outIterator,
.success = true
};
}
template<typename TFrom, typename TFromTraits, typename TTo, typename TToTraits, typename TToAllocator>
bool convertStringType(std::basic_string_view<TFrom, TFromTraits> strFrom, std::basic_string<TTo, TToTraits, TToAllocator>& outString)
{
if constexpr (std::is_same_v<TTo, TFrom>)
{
outString += strFrom;
return true;
}
else
{
return convertStringType<TTo>(strFrom, std::back_inserter(outString)).success;
}
}
template<typename TFrom, typename TTo, typename TToTraits, typename TToAllocator>
bool convertStringType(const TFrom* strFrom, std::basic_string<TTo, TToTraits, TToAllocator>& outString)
{
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

View File

@ -21,6 +21,9 @@ namespace mijin
// public types
//
struct Type_; // use as typevar
struct Any_; // use as placeholder in templates
template<typename T>
struct always_false
{
@ -95,11 +98,88 @@ struct map_template {
using type_t = TTemplate<TPredicate<T>>;
};
template<typename T, typename... Types>
struct is_any_type : std::disjunction<std::is_same<T, Types>...> {};
template<template<typename...> typename TTemplate, typename TType>
struct is_template_instance : std::false_type {};
template<template<typename...> typename TTemplate, typename... TArgs>
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
template<template<typename...> typename TTemplate, typename TType>
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
namespace impl
{
template<template<typename...> typename TTemplate, typename... TArgsTTmpl>
struct tmpl_param_comparator
{
template<typename TTmpl, typename TInstance>
static constexpr bool compare_single = std::is_same_v<TTmpl, TInstance> or std::is_same_v<TTmpl, Any_>;
template<typename T>
struct matches : std::false_type {};
// original (which MSVC didn't like for some reason :/)
// template<typename... TArgsInstance>
// struct matches<TTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
template<template<typename...> typename TOtherTemplate, typename... TArgsInstance> requires(is_template_instance_v<TTemplate, TOtherTemplate<TArgsInstance...>>)
struct matches<TOtherTemplate<TArgsInstance...>> : std::bool_constant<(compare_single<TArgsTTmpl, TArgsInstance> && ...)> {};
template<typename T>
using matches_t = matches<T>;
};
}
template<typename TTemplate, typename TType>
struct match_template_type : std::false_type {};
template<template<typename...> typename TTemplate, typename TType, typename... TArgs>
struct match_template_type<TTemplate<TArgs...>, TType> : impl::tmpl_param_comparator<TTemplate, TArgs...>::template matches_t<TType> {};
template<typename TTemplate, typename TType>
constexpr bool match_template_type_v = match_template_type<TTemplate, TType>::value;
// similar to std::is_same, but allows placeholders
// - is_type_or_impl<some_tmpl<Type_>, some_type> resolves to some_tmpl<some_type>
// - is_type_or_impl<some_tmpl<Any_, int>, some_type> checks if some_type is an instance of some_tmpl with int as 2nd parameter
template<typename TMatch, typename TConcrete>
struct type_matches
{
using type = std::is_same<TMatch, TConcrete>;
};
template<template<typename> typename TTmplMatch, typename TConcrete>
struct type_matches<TTmplMatch<Type_>, TConcrete>
{
using type = TTmplMatch<TConcrete>;
};
template<template<typename...> typename TTmplMatch, typename TConcrete, typename... TArgs>
struct type_matches<TTmplMatch<TArgs...>, TConcrete>
{
using type = match_template_type<TTmplMatch<TArgs...>, TConcrete>;
};
template<typename TMatch, typename TConcrete>
using type_matches_t = type_matches<TMatch, TConcrete>::type;
template<typename TMatch, typename TConcrete>
inline constexpr bool type_matches_v = type_matches_t<TMatch, TConcrete>::value;
template<typename TConcrete, typename... TMatches>
struct is_any_type : std::disjunction<std::is_same<TConcrete, TMatches>...> {};
template<typename TConcrete, typename... TMatches>
static constexpr bool is_any_type_v = is_any_type<TConcrete, TMatches...>::value;
template<typename TConcrete, typename... TMatches>
struct match_any_type : std::disjunction<type_matches_t<TMatches, TConcrete>...> {};
template<typename TConcrete, typename... TMatches>
static constexpr bool match_any_type_v = match_any_type<TConcrete, TMatches...>::value;
template<typename T, typename... Types>
static constexpr bool is_any_type_v = is_any_type<T, Types...>::value;
concept union_type = match_any_type_v<T, Types...>;
template<typename TElement, typename TCollection>
struct is_type_member;
@ -112,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>>;
@ -139,14 +231,39 @@ struct TypeAtHelper<0, TArg, TArgs...>
template<std::size_t I, typename... TArgs>
using type_at_t = TypeAtHelper<I, TArgs...>::type_t;
template<template<typename...> typename TTemplate, typename TType>
struct is_template_instance : std::false_type {};
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
struct detect_or
{
using type = TDefault;
static constexpr bool detected = false;
};
template<template<typename...> typename TTemplate, typename... TArgs>
struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
requires requires { typename TOper<TArgs...>; }
struct detect_or<TDefault, TOper, TArgs...>
{
using type = TOper<TArgs...>;
static constexpr bool detected = true;
};
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
using detect_or_t = detect_or<TDefault, TOper, TArgs...>::type;
template<template<typename...> typename TTemplate, typename TType>
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
struct empty_type {};
template<typename T, bool enable>
struct optional_base
{
using type = T;
};
template<typename T>
struct optional_base<T, false>
{
using type = empty_type;
};
template<typename T, bool enable>
using optional_base_t = optional_base<T, enable>::type;
//
// public functions
@ -158,6 +275,34 @@ decltype(auto) delayEvaluation(TType&& value)
return static_cast<TType&&>(value);
}
#define MIJIN_STATIC_TESTS 1
#if MIJIN_STATIC_TESTS
namespace test
{
template<typename T, typename U>
struct MyTemplate {};
static_assert(match_template_type_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
static_assert(match_template_type_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
static_assert(type_matches_v<MyTemplate<Any_, int>, MyTemplate<int, int>>);
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, int>>);
static_assert(!type_matches_v<MyTemplate<double, int>, MyTemplate<int, int>>);
static_assert(type_matches_v<MyTemplate<Any_, Any_>, MyTemplate<int, double>>);
static_assert(type_matches_v<std::is_pointer<Type_>, void*>);
static_assert(union_type<int, int>);
static_assert(!union_type<int>);
static_assert(!union_type<int, double>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<int, int>>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, int>>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<Any_, Any_>>);
static_assert(union_type<MyTemplate<int, int>, MyTemplate<double, double>, MyTemplate<Any_, Any_>>);
static_assert(!union_type<int*, int>);
static_assert(union_type<int*, std::is_pointer<Type_>>);
static_assert(!union_type<int, std::is_pointer<Type_>>);
}
#endif
} // namespace mijin
#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)

View File

@ -22,3 +22,7 @@
#if defined(RELATIVE)
#undef RELATIVE
#endif
#if defined(DEBUG)
#undef DEBUG
#endif

View File

@ -35,6 +35,7 @@ namespace mijin
struct FileInfo
{
fs::path path;
/// either file size in bytes, or number of entries if folder
std::size_t size = 0;
bool exists : 1 = false;
bool isFolder : 1 = false;
@ -79,7 +80,8 @@ class FileSystemAdapter
public:
virtual ~FileSystemAdapter() = default;
[[nodiscard]] virtual fs::path getHomeFolder() = 0;
[[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;
[[nodiscard]] virtual Optional<fs::path> getNativePath(const fs::path& /* file */) { return NULL_OPTIONAL; }

View File

@ -0,0 +1,160 @@
#include "./memory.hpp"
#include "../io/stream.hpp"
namespace mijin
{
std::vector<FileInfo> MemoryFileSystemAdapter::listFiles(const fs::path& folder)
{
const detail::MemoryFolder* folderObj = findFolder(folder);
if (folderObj == nullptr)
{
return {};
}
std::vector<FileInfo> result;
result.reserve(folderObj->folders.size() + folderObj->files.size());
for (const auto& [name, subFolder] : folderObj->folders)
{
result.push_back(folderInfo(folder / name, subFolder));
}
for (const auto& [name, file] : folderObj->files)
{
result.push_back(fileInfo(folder / name, file));
}
return result;
}
FileInfo MemoryFileSystemAdapter::getFileInfo(const fs::path& file)
{
#if 0 // shouldn't be necessary
// empty means root
if (file.empty())
{
return {
.path = {},
.size = root_.folders.size() + root_.files.size(),
.exists = true,
.isFolder = true
};
}
#endif
const detail::MemoryFolder* folderObj = findFolder(file.parent_path());
if (folderObj == nullptr)
{
return {};
}
const std::string filename = file.filename().generic_string();
if (auto itFolder = folderObj->folders.find(filename); itFolder != folderObj->folders.end())
{
return folderInfo(file, itFolder->second);
}
if (auto itFile = folderObj->files.find(filename); itFile != folderObj->files.end())
{
return fileInfo(file, itFile->second);
}
return {};
}
StreamError MemoryFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
{
if (mode != FileOpenMode::READ)
{
return StreamError::IO_ERROR;
}
const detail::MemoryFolder* folderObj = findFolder(path.parent_path());
if (folderObj == nullptr)
{
return StreamError::IO_ERROR;
}
auto itFile = folderObj->files.find(path.filename().generic_string());
if (itFile == folderObj->files.end())
{
return StreamError::IO_ERROR;
}
std::unique_ptr<MemoryStream> stream = std::make_unique<MemoryStream>();
stream->openRO(itFile->second.data);
outStream = std::move(stream);
return StreamError::SUCCESS;
}
bool MemoryFileSystemAdapter::addFile(const fs::path& path, std::span<const std::uint8_t> data, Overwrite overwrite, CopyData copyData)
{
detail::MemoryFolder& folder = *findFolder(path.parent_path(), true);
std::string filename = path.filename().generic_string();
if (folder.folders.contains(filename))
{
return false;
}
if (!overwrite && folder.files.contains(filename))
{
return false;
}
if (copyData)
{
data = fileData_.emplace_back(data.begin(), data.end());
}
folder.files.emplace(std::move(filename), detail::MemoryFile{.data = data});
return true;
}
void MemoryFileSystemAdapter::addFolder(const fs::path& path)
{
(void) findFolder(path, true);
}
detail::MemoryFolder* MemoryFileSystemAdapter::findFolder(const fs::path& path, bool create) MIJIN_NOEXCEPT
{
detail::MemoryFolder* folder = &root_;
for (const fs::path& part : path)
{
std::string partname = part.generic_string();
auto it = folder->folders.find(partname);
if (it == folder->folders.end())
{
if (!create)
{
return nullptr;
}
folder = &folder->folders[std::move(partname)];
}
else
{
folder = &it->second;
}
}
return folder;
}
FileInfo MemoryFileSystemAdapter::folderInfo(const fs::path& path, const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT
{
return {
.path = path,
.size = folder.folders.size() + folder.files.size(),
.exists = true,
.isFolder = true
};
}
FileInfo MemoryFileSystemAdapter::fileInfo(const fs::path& path, const detail::MemoryFile& file) const MIJIN_NOEXCEPT
{
return {
.path = path,
.size = file.data.size(),
.exists = true
};
}
}

View File

@ -0,0 +1,55 @@
#pragma once
#if !defined(MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED)
#define MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED 1
#include <variant>
#include <vector>
#include "../container/vector_map.hpp"
#include "../util/flag.hpp"
#include "../virtual_filesystem/filesystem.hpp"
namespace mijin
{
namespace detail
{
struct MemoryFile
{
std::span<const std::uint8_t> data;
};
struct MemoryFolder
{
VectorMap<std::string, MemoryFile, std::allocator<std::string>, std::allocator<MemoryFile>> files; // TODO: make the FS library allocator aware
VectorMap<std::string, MemoryFolder, std::allocator<std::string>, std::allocator<MemoryFolder>> folders;
};
}
class MemoryFileSystemAdapter : public FileSystemAdapter
{
public:
MIJIN_DEFINE_FLAG(Overwrite);
MIJIN_DEFINE_FLAG(CopyData);
private:
detail::MemoryFolder root_;
std::vector<std::vector<std::uint8_t>> fileData_;
public:
[[nodiscard]] std::vector<FileInfo> listFiles(const fs::path& folder) override;
[[nodiscard]] FileInfo getFileInfo(const fs::path& file) override;
[[nodiscard]] StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
bool addFile(const fs::path& path, std::span<const std::uint8_t> data, Overwrite overwrite = Overwrite::NO, CopyData copyData = CopyData::NO);
bool addFile(const fs::path& path, std::span<const std::uint8_t> data, CopyData copyData)
{
return addFile(path, data, Overwrite::NO, copyData);
}
void addFolder(const fs::path& path);
private:
detail::MemoryFolder* findFolder(const fs::path& path, bool create = false) MIJIN_NOEXCEPT;
FileInfo folderInfo(const fs::path& path, const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT;
FileInfo fileInfo(const fs::path& path, const detail::MemoryFile& file) const MIJIN_NOEXCEPT;
};
} // namespace mijin
#endif // !defined(MIJIN_SOURCE_MIJIN_VIRTUAL_FILESYSTEM_MEMORY_HPP_INCLUDED)

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

View File

@ -2,6 +2,7 @@
#include "./stacked.hpp"
#include <algorithm>
#include "../detect.hpp"
namespace mijin
{
@ -35,7 +36,19 @@ fs::path StackedFileSystemAdapter::getHomeFolder()
if (adapters_.empty()) {
return fs::path();
}
#if MIJIN_COMPILER == MIJIN_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4996) // yeah, we're using a deprecated function here, in order to implement another deprecated function ¯\_(ツ)_/¯
#elif MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
return adapters_.front()->getHomeFolder();
#if MIJIN_COMPILER == MIJIN_COMPILER_MSVC
#pragma warning(pop)
#elif MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma GCC diagnostic pop
#endif
}
std::vector<FileInfo> StackedFileSystemAdapter::listFiles(const fs::path& folder)

View File

@ -36,12 +36,13 @@ public:
void getAllPaths(const fs::path &path, std::vector<PathReference> &outPaths) override;
using FileSystemAdapter::getAllPaths;
inline void addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) {
inline FileSystemAdapter* addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) {
adapters_.push_back(std::move(adapter));
return adapters_.back().get();
}
template<typename TAdapter, typename... TArgs>
inline void emplaceAdapter(TArgs&&... args) {
addAdapter(std::make_unique<TAdapter>(std::forward<TArgs>(args)...));
inline TAdapter* emplaceAdapter(TArgs&&... args) {
return static_cast<TAdapter*>(addAdapter(std::make_unique<TAdapter>(std::forward<TArgs>(args)...)));
}
};