diff --git a/source/mijin/async/boxed_signal.hpp b/source/mijin/async/boxed_signal.hpp new file mode 100644 index 0000000..ef94485 --- /dev/null +++ b/source/mijin/async/boxed_signal.hpp @@ -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 typename TAllocator, typename... TArgs> +class BaseBoxedSignal : private BoxedObject> +{ +private: + using base_t = BoxedObject>; +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 +using BoxedSignal = BaseBoxedSignal; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_ASYNC_BOXED_SIGNAL_HPP_INCLUDED) diff --git a/source/mijin/async/coroutine.cpp b/source/mijin/async/coroutine.cpp index 3ad5681..02f9da3 100644 --- a/source/mijin/async/coroutine.cpp +++ b/source/mijin/async/coroutine.cpp @@ -26,205 +26,15 @@ namespace mijin namespace impl { -thread_local TaskLoop::StoredTask* gCurrentTask = nullptr; +thread_local std::shared_ptr 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 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 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 threadName; - (void) std::snprintf(threadName.data(), 16, "Task Worker %lu", static_cast(workerId)); - setCurrentThreadName(threadName.data()); - - while (!stopToken.stop_requested()) - { - // try to fetch a task to run - std::optional 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 diff --git a/source/mijin/async/coroutine.hpp b/source/mijin/async/coroutine.hpp index 2433af9..f9d2eee 100644 --- a/source/mijin/async/coroutine.hpp +++ b/source/mijin/async/coroutine.hpp @@ -10,6 +10,7 @@ #endif #include +#include #include #include #include @@ -20,8 +21,10 @@ #include "./message_queue.hpp" #include "../container/optional.hpp" #include "../internal/common.hpp" +#include "../memory/memutil.hpp" #include "../util/flag.hpp" #include "../util/iterators.hpp" +#include "../util/misc.hpp" #include "../util/traits.hpp" #if MIJIN_COROUTINE_ENABLE_DEBUG_INFO #include "../debug/stacktrace.hpp" @@ -63,9 +66,10 @@ enum class TaskStatus template struct TaskState; +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> class TaskLoop; -template +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> class TaskBase; #if MIJIN_COROUTINE_ENABLE_CANCEL @@ -103,6 +107,7 @@ public: } inline void cancel() const MIJIN_NOEXCEPT; + [[nodiscard]] inline Optional getLocation() const MIJIN_NOEXCEPT; #if MIJIN_COROUTINE_ENABLE_DEBUG_INFO inline Optional getCreationStack() const MIJIN_NOEXCEPT; #endif @@ -111,6 +116,7 @@ struct TaskSharedState { std::atomic_bool cancelled_ = false; TaskHandle subTask; + std::source_location sourceLoc; #if MIJIN_COROUTINE_ENABLE_DEBUG_INFO Stacktrace creationStack_; #endif @@ -245,16 +251,68 @@ struct TaskAwaitableSuspend } }; -template +namespace impl +{ +template +using default_is_valid = T::default_is_valid_t; +} + +template typename TAllocator> +struct TaskAllocatorTraits +{ + static constexpr bool default_is_valid_v = detect_or_t>::value; + + template + static TAllocator create() + { + auto taskLoop = TaskLoop::currentOpt(); + if (taskLoop != nullptr) + { + return TAllocator(taskLoop->getAllocator()); + } + if constexpr (std::is_default_constructible_v>) + { + return TAllocator(); + } + else + { + MIJIN_FATAL("Could not create task allocator."); + } + } +}; + +template<> +struct TaskAllocatorTraits +{ + static constexpr bool default_is_valid_v = true; + + template + static std::allocator create() noexcept + { + return std::allocator(); + } +}; + +template typename TAllocator, typename T> +TAllocator makeTaskAllocator() +{ + return TaskAllocatorTraits::template create(); +} + +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> struct TaskPromise : impl::TaskReturn> { using handle_t = std::coroutine_handle; using task_t = typename TTraits::task_t; using result_t = typename TTraits::result_t; + [[no_unique_address]] TAllocator allocator_; TaskState state_; - std::shared_ptr sharedState_ = std::make_shared(); - TaskLoop* loop_ = nullptr; + std::shared_ptr sharedState_; + TaskLoop* loop_ = nullptr; + + explicit TaskPromise(TAllocator allocator = makeTaskAllocator()) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : allocator_(std::move(allocator)), sharedState_(std::allocate_shared(TAllocator(allocator_))) {} constexpr task_t get_return_object() MIJIN_NOEXCEPT { return task_t(handle_t::from_promise(*this)); } constexpr TaskAwaitableSuspend initial_suspend() MIJIN_NOEXCEPT { return {}; } @@ -271,9 +329,10 @@ struct TaskPromise : impl::TaskReturn - auto await_transform(FuturePtr future) MIJIN_NOEXCEPT + auto await_transform(FuturePtr future, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!"); + sharedState_->sourceLoc = std::move(sourceLoc); TaskAwaitableFuture awaitable{future}; if (!awaitable.await_ready()) { @@ -287,17 +346,18 @@ struct TaskPromise : impl::TaskReturn - auto await_transform(TaskBase task) MIJIN_NOEXCEPT + auto await_transform(TaskBase task, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!"); // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult) - auto future = delayEvaluation(loop_)->addTask(std::move(task), &sharedState_->subTask); // hackidyhack: delay evaluation of the type of loop_ as it is only forward-declared here - return await_transform(future); + auto future = delayEvaluation(loop_)->addTaskImpl(std::move(task), &sharedState_->subTask); // hackidyhack: delay evaluation of the type of loop_ as it is only forward-declared here + return await_transform(future, std::move(sourceLoc)); } template - auto await_transform(Signal& signal) MIJIN_NOEXCEPT + auto await_transform(Signal& signal, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { auto data = std::make_shared>(); + sharedState_->sourceLoc = std::move(sourceLoc); signal.connect([this, data](TFirstArg arg0, TSecondArg arg1, TArgs... args) mutable { *data = std::make_tuple(std::move(arg0), std::move(arg1), std::move(args)...); @@ -309,9 +369,10 @@ struct TaskPromise : impl::TaskReturn - auto await_transform(Signal& signal) MIJIN_NOEXCEPT + auto await_transform(Signal& signal, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { auto data = std::make_shared(); + sharedState_->sourceLoc = std::move(sourceLoc); signal.connect([this, data](TFirstArg arg0) mutable { *data = std::move(arg0); @@ -322,8 +383,9 @@ struct TaskPromise : impl::TaskReturn& signal) MIJIN_NOEXCEPT + auto await_transform(Signal<>& signal, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { + sharedState_->sourceLoc = std::move(sourceLoc); signal.connect([this]() { state_.status = TaskStatus::SUSPENDED; @@ -333,24 +395,39 @@ struct TaskPromise : impl::TaskReturnsourceLoc = std::move(sourceLoc); state_.status = TaskStatus::SUSPENDED; return std::suspend_always(); } - std::suspend_never await_transform(std::suspend_never) MIJIN_NOEXCEPT { + std::suspend_never await_transform(std::suspend_never, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { + sharedState_->sourceLoc = std::move(sourceLoc); return std::suspend_never(); } - TaskAwaitableSuspend await_transform(TaskAwaitableSuspend) MIJIN_NOEXCEPT + TaskAwaitableSuspend await_transform(TaskAwaitableSuspend, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { + sharedState_->sourceLoc = std::move(sourceLoc); state_.status = TaskStatus::SUSPENDED; return TaskAwaitableSuspend(); } + + // make sure the allocators are also used for the promise itself + void* operator new(std::size_t size) + { + return makeTaskAllocator().allocate((size - 1) / sizeof(std::max_align_t) + 1); + } + + void operator delete(void* ptr, std::size_t size) noexcept + { + TaskPromise* self = static_cast(ptr); + self->allocator_.deallocate(static_cast(ptr), (size - 1) / sizeof(std::max_align_t) + 1); + } }; -template +template typename TAllocator> class [[nodiscard("Tasks should either we awaited or added to a loop.")]] TaskBase { public: @@ -362,7 +439,7 @@ public: using result_t = TResult; }; public: - using promise_type = TaskPromise; + using promise_type = TaskPromise; using handle_t = typename promise_type::handle_t; private: handle_t handle_; @@ -415,11 +492,11 @@ private: [[nodiscard]] constexpr handle_t handle() const MIJIN_NOEXCEPT { return handle_; } [[nodiscard]] - constexpr TaskLoop* getLoop() MIJIN_NOEXCEPT + constexpr TaskLoop* getLoop() MIJIN_NOEXCEPT { return handle_.promise().loop_; } - constexpr void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT + constexpr void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT { // MIJIN_ASSERT(handle_.promise().loop_ == nullptr // || handle_.promise().loop_ == loop @@ -427,12 +504,24 @@ private: handle_.promise().loop_ = loop; } - friend class TaskLoop; + friend class TaskLoop; - template + template typename TAllocator2> friend class WrappedTask; }; +template +struct is_task : std::false_type {}; +template typename TAllocator> +struct is_task> : std::true_type {}; + +template +inline constexpr bool is_task_v = is_task::value; + +template +concept task_type = is_task_v; + +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> class WrappedTaskBase { public: @@ -444,7 +533,7 @@ public: virtual void resume() = 0; virtual void* raw() MIJIN_NOEXCEPT = 0; virtual std::coroutine_handle<> handle() MIJIN_NOEXCEPT = 0; - virtual void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT = 0; + virtual void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT = 0; virtual std::shared_ptr& sharedState() MIJIN_NOEXCEPT = 0; [[nodiscard]] inline bool canResume() { @@ -453,8 +542,8 @@ public: } }; -template -class WrappedTask : public WrappedTaskBase +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +class WrappedTask : public WrappedTaskBase { private: TTask task_; @@ -480,57 +569,95 @@ public: void resume() override { task_.resume(); } void* raw() MIJIN_NOEXCEPT override { return &task_; } std::coroutine_handle<> handle() MIJIN_NOEXCEPT override { return task_.handle(); } - void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT override { task_.setLoop(loop); } + void setLoop(TaskLoop* loop) MIJIN_NOEXCEPT override { task_.setLoop(loop); } virtual std::shared_ptr& sharedState() MIJIN_NOEXCEPT override { return task_.sharedState(); } }; -template -std::unique_ptr> wrapTask(TTask&& task) MIJIN_NOEXCEPT +template typename TAllocator> +auto wrapTask(TAllocator> allocator, TTask&& task) { - return std::make_unique>(std::forward(task)); + using wrapped_task_t = WrappedTask; + using deleter_t = AllocatorDeleter>; + using allocator_t = TAllocator; + + wrapped_task_t* ptr = ::new (allocator.allocate(1)) wrapped_task_t(std::forward(task)); + return std::unique_ptr(ptr, AllocatorDeleter(std::move(allocator))); } +template typename TAllocator> class TaskLoop { public: MIJIN_DEFINE_FLAG(CanContinue); MIJIN_DEFINE_FLAG(IgnoreWaiting); - using wrapped_task_t = WrappedTaskBase; - using wrapped_task_base_ptr_t = std::unique_ptr; + using wrapped_task_t = WrappedTaskBase; + using wrapped_allocator_t = TAllocator; + using wrapped_deleter_t = AllocatorDeleter; + using wrapped_task_base_ptr_t = std::unique_ptr; struct StoredTask { + using set_future_t = std::function; wrapped_task_base_ptr_t task; - std::function setFuture; + set_future_t setFuture; std::any resultData; + + StoredTask(wrapped_task_base_ptr_t&& task_, set_future_t&& setFuture_, std::any&& resultData_) + : task(std::move(task_)), setFuture(std::move(setFuture_)), resultData(std::move(resultData_)) {} + template + StoredTask(TAllocator allocator_) : task(nullptr, wrapped_deleter_t(wrapped_allocator_t(allocator_))) {} }; using exception_handler_t = std::function; + using allocator_t = TAllocator; protected: - using task_vector_t = std::vector; + using task_vector_t = std::vector>; template using wrapped_task_ptr_t = std::unique_ptr>; exception_handler_t uncaughtExceptionHandler_; + [[no_unique_address]] allocator_t allocator_; public: - TaskLoop() MIJIN_NOEXCEPT = default; + explicit TaskLoop(allocator_t allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : allocator_(std::move(allocator)) {}; TaskLoop(const TaskLoop&) = delete; TaskLoop(TaskLoop&&) = delete; virtual ~TaskLoop() MIJIN_NOEXCEPT = default; + [[nodiscard]] + const allocator_t& getAllocator() const MIJIN_NOEXCEPT { return allocator_; } + TaskLoop& operator=(const TaskLoop&) = delete; TaskLoop& operator=(TaskLoop&&) = delete; void setUncaughtExceptionHandler(exception_handler_t handler) MIJIN_NOEXCEPT { uncaughtExceptionHandler_ = std::move(handler); } template - inline FuturePtr addTask(TaskBase task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT; + FuturePtr addTaskImpl(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT; + + template + FuturePtr addTask(TaskBase task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT + { + static_assert(TaskAllocatorTraits::default_is_valid_v, "Allocator is not valid when default constructed, use makeTask() instead."); + return addTaskImpl(std::move(task), outHandle); + } + + template + auto makeTask(TCoro&& coro, TaskHandle& outHandle, TArgs&&... args) MIJIN_NOEXCEPT; + + template + auto makeTask(TCoro&& coro, TArgs&&... args) MIJIN_NOEXCEPT + { + TaskHandle dummy; + return makeTask(std::forward(coro), dummy, std::forward(args)...); + } virtual void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT = 0; virtual void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT = 0; [[nodiscard]] static TaskLoop& current() MIJIN_NOEXCEPT; + [[nodiscard]] static TaskLoop* currentOpt() MIJIN_NOEXCEPT; protected: inline TaskStatus tickTask(StoredTask& task); protected: @@ -542,17 +669,29 @@ protected: template using Task = TaskBase; -class SimpleTaskLoop : public TaskLoop +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +class BaseSimpleTaskLoop : public TaskLoop { private: + using base_t = TaskLoop; + using typename TaskLoop::task_vector_t; + using typename TaskLoop::allocator_t; + using typename TaskLoop::StoredTask; + using typename TaskLoop::CanContinue; + using typename TaskLoop::IgnoreWaiting; + + using base_t::allocator_; task_vector_t tasks_; task_vector_t newTasks_; task_vector_t::iterator currentTask_; MessageQueue queuedTasks_; std::thread::id threadId_; - +public: + explicit BaseSimpleTaskLoop(const allocator_t& allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) + : base_t(std::move(allocator)), tasks_(TAllocator(allocator_)), newTasks_(TAllocator(allocator_)), + queuedTasks_(constructArray::BUFFER_SIZE>(allocator_)) {} public: // TaskLoop implementation - void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override; + void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override; void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override; public: // public interface @@ -562,23 +701,39 @@ public: // public interface inline CanContinue tick(); inline void runUntilDone(IgnoreWaiting ignoreWaiting = IgnoreWaiting::NO); inline void cancelAllTasks() MIJIN_NOEXCEPT; - [[nodiscard]] inline std::vector getAllTasks() const MIJIN_NOEXCEPT; + [[nodiscard]] inline std::vector> getAllTasks() const MIJIN_NOEXCEPT; private: inline void assertCorrectThread() { MIJIN_ASSERT(threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id(), "Unsafe to TaskLoop from different thread!"); } }; +using SimpleTaskLoop = BaseSimpleTaskLoop<>; -class MultiThreadedTaskLoop : public TaskLoop +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +class BaseMultiThreadedTaskLoop : public TaskLoop { private: + using base_t = TaskLoop; + using typename base_t::task_vector_t; + using typename base_t::allocator_t; + using typename base_t::StoredTask; + + using base_t::allocator_; task_vector_t parkedTasks_; // buffer for tasks that don't fit into readyTasks_ MessageQueue queuedTasks_; // tasks that should be appended to parked tasks MessageQueue readyTasks_; // task queue to send tasks to a worker thread MessageQueue returningTasks_; // task that have executed on a worker thread and return for further processing std::jthread managerThread_; - std::vector workerThreads_; + std::vector> workerThreads_; +public: + explicit BaseMultiThreadedTaskLoop(allocator_t allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) + : base_t(std::move(allocator)), + parkedTasks_(TAllocator(allocator_)), + queuedTasks_(constructArray::BUFFER_SIZE>(allocator_)), + readyTasks_(constructArray::BUFFER_SIZE>(allocator_)), + returningTasks_(constructArray::BUFFER_SIZE>(allocator_)), + workerThreads_(TAllocator(allocator_)) {} public: // TaskLoop implementation - void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override; + void transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT override; void addStoredTask(StoredTask&& storedTask) MIJIN_NOEXCEPT override; public: // public interface @@ -587,7 +742,14 @@ public: // public interface private: // private stuff void managerThread(std::stop_token stopToken); void workerThread(std::stop_token stopToken, std::size_t workerId); + + static StoredTask*& getCurrentTask() + { + static thread_local StoredTask* task = nullptr; + return task; + } }; +using MultiThreadedTaskLoop = BaseMultiThreadedTaskLoop<>; // // public functions @@ -595,12 +757,12 @@ private: // private stuff namespace impl { -extern thread_local TaskLoop::StoredTask* gCurrentTask; +extern thread_local std::shared_ptr gCurrentTaskState; inline void throwIfCancelled() { #if MIJIN_COROUTINE_ENABLE_CANCEL - if (gCurrentTask->task->sharedState()->cancelled_) + if (gCurrentTaskState->cancelled_) { throw TaskCancelled(); } @@ -617,6 +779,15 @@ void TaskHandle::cancel() const MIJIN_NOEXCEPT } } +Optional TaskHandle::getLocation() const MIJIN_NOEXCEPT +{ + if (std::shared_ptr state = state_.lock()) + { + return state->sourceLoc; + } + return NULL_OPTIONAL; +} + #if MIJIN_COROUTINE_ENABLE_DEBUG_INFO Optional TaskHandle::getCreationStack() const MIJIN_NOEXCEPT { @@ -628,8 +799,8 @@ Optional TaskHandle::getCreationStack() const MIJIN_NOEXCEPT } #endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO -template -TaskBase::~TaskBase() MIJIN_NOEXCEPT +template typename TAllocator> +TaskBase::~TaskBase() MIJIN_NOEXCEPT { if (handle_) { @@ -637,13 +808,14 @@ TaskBase::~TaskBase() MIJIN_NOEXCEPT } } +template typename TAllocator> template -inline FuturePtr TaskLoop::addTask(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT +FuturePtr TaskLoop::addTaskImpl(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT { MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!"); task.setLoop(this); - auto future = std::make_shared>(); + FuturePtr future = std::allocate_shared>(TAllocator>(allocator_), allocator_); auto setFuture = &setFutureHelper; if (outHandle != nullptr) @@ -652,26 +824,35 @@ inline FuturePtr TaskLoop::addTask(TaskBase task, TaskHandle* } // add tasks to a seperate vector first as we might be running another task right now - addStoredTask(StoredTask{ - .task = wrapTask(std::move(task)), - .setFuture = setFuture, - .resultData = future - }); + TAllocator>> allocator(allocator_); + addStoredTask(StoredTask(wrapTask(std::move(allocator), std::move(task)), std::move(setFuture), future)); return future; } -inline TaskStatus TaskLoop::tickTask(StoredTask& task) +template typename TAllocator> +template +auto TaskLoop::makeTask(TCoro&& coro, TaskHandle& outHandle, TArgs&&... args) MIJIN_NOEXCEPT +{ + TaskLoop* previousLoop = currentLoopStorage(); + currentLoopStorage() = this; + auto result = addTaskImpl(std::invoke(std::forward(coro), std::forward(args)...), &outHandle); + currentLoopStorage() = previousLoop; + return result; +} + +template typename TAllocator> +TaskStatus TaskLoop::tickTask(StoredTask& task) { TaskStatus status = {}; - impl::gCurrentTask = &task; + impl::gCurrentTaskState = task.task->sharedState(); do { task.task->resume(); status = task.task ? task.task->status() : TaskStatus::WAITING; // no inner task -> task switch context (and will be removed later) } while (status == TaskStatus::RUNNING); - impl::gCurrentTask = nullptr; + impl::gCurrentTaskState = nullptr; #if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING if (task.task && task.task->exception()) @@ -706,22 +887,31 @@ inline TaskStatus TaskLoop::tickTask(StoredTask& task) return status; } -/* static */ inline auto TaskLoop::current() MIJIN_NOEXCEPT -> TaskLoop& +template typename TAllocator> +/* static */ inline auto TaskLoop::current() MIJIN_NOEXCEPT -> TaskLoop& { MIJIN_ASSERT(currentLoopStorage() != nullptr, "Attempting to fetch current loop while no coroutine is running!"); return *currentLoopStorage(); } -/* static */ auto TaskLoop::currentLoopStorage() MIJIN_NOEXCEPT -> TaskLoop*& +template typename TAllocator> +/* static */ inline auto TaskLoop::currentOpt() MIJIN_NOEXCEPT -> TaskLoop* +{ + return currentLoopStorage(); +} + +template typename TAllocator> +/* static */ auto TaskLoop::currentLoopStorage() MIJIN_NOEXCEPT -> TaskLoop*& { static thread_local TaskLoop* storage = nullptr; return storage; } +template typename TAllocator> template -/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT +/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT { - TaskBase& task = *static_cast*>(storedTask.task->raw()); + TaskBase& task = *static_cast*>(storedTask.task->raw()); auto future = std::any_cast>(storedTask.resultData); if constexpr (!std::is_same_v) @@ -734,9 +924,10 @@ template } } -inline std::suspend_always switchContext(TaskLoop& taskLoop) +template typename TAllocator> +inline std::suspend_always switchContext(TaskLoop& taskLoop) { - TaskLoop& currentTaskLoop = TaskLoop::current(); + TaskLoop& currentTaskLoop = TaskLoop::current(); if (¤tTaskLoop == &taskLoop) { return {}; } @@ -744,11 +935,68 @@ inline std::suspend_always switchContext(TaskLoop& taskLoop) return {}; } -inline auto SimpleTaskLoop::tick() -> CanContinue +template typename TAllocator> +void BaseSimpleTaskLoop::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)); +} + +template typename TAllocator> +void BaseSimpleTaskLoop::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 (TaskLoop::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)); + } +} + +template typename TAllocator> +std::size_t BaseSimpleTaskLoop::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; +} + +template typename TAllocator> +inline auto BaseSimpleTaskLoop::tick() -> CanContinue { // set current taskloop - MIJIN_ASSERT(currentLoopStorage() == nullptr, "Trying to tick a loop from a coroutine, this is not supported."); - currentLoopStorage() = this; + MIJIN_ASSERT(TaskLoop::currentLoopStorage() == nullptr, "Trying to tick a loop from a coroutine, this is not supported."); + TaskLoop::currentLoopStorage() = this; threadId_ = std::this_thread::get_id(); // move over all tasks from newTasks @@ -791,7 +1039,7 @@ inline auto SimpleTaskLoop::tick() -> CanContinue continue; } - status = tickTask(task); + status = base_t::tickTask(task); if (status == TaskStatus::SUSPENDED || status == TaskStatus::YIELDED) { @@ -799,7 +1047,7 @@ inline auto SimpleTaskLoop::tick() -> CanContinue } } // reset current loop - currentLoopStorage() = nullptr; + TaskLoop::currentLoopStorage() = nullptr; // remove any tasks that have been transferred to another queue it = std::remove_if(tasks_.begin(), tasks_.end(), [](const StoredTask& task) { @@ -810,7 +1058,8 @@ inline auto SimpleTaskLoop::tick() -> CanContinue return canContinue; } -inline void SimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting) +template typename TAllocator> +void BaseSimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting) { while (!tasks_.empty() || !newTasks_.empty()) { @@ -822,7 +1071,8 @@ inline void SimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting) } } -inline void SimpleTaskLoop::cancelAllTasks() MIJIN_NOEXCEPT +template typename TAllocator> +void BaseSimpleTaskLoop::cancelAllTasks() MIJIN_NOEXCEPT { for (StoredTask& task : mijin::chain(tasks_, newTasks_)) { @@ -835,9 +1085,10 @@ inline void SimpleTaskLoop::cancelAllTasks() MIJIN_NOEXCEPT } } -inline std::vector SimpleTaskLoop::getAllTasks() const MIJIN_NOEXCEPT +template typename TAllocator> +std::vector> BaseSimpleTaskLoop::getAllTasks() const MIJIN_NOEXCEPT { - std::vector result; + std::vector> result((TAllocator(TaskLoop::allocator_))); for (const StoredTask& task : mijin::chain(tasks_, newTasks_)) { result.emplace_back(task.task->sharedState()); @@ -845,6 +1096,151 @@ inline std::vector SimpleTaskLoop::getAllTasks() const MIJIN_NOEXCEP return result; } +template typename TAllocator> +void BaseMultiThreadedTaskLoop::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 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 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)); + } +} + +template typename TAllocator> +void BaseMultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t workerId) // NOLINT(performance-unnecessary-value-param) +{ + TaskLoop::currentLoopStorage() = this; // forever (on this thread) + + std::array threadName; + (void) std::snprintf(threadName.data(), 16, "Task Worker %lu", static_cast(workerId)); + // setCurrentThreadName(threadName.data()); + + while (!stopToken.stop_requested()) + { + // try to fetch a task to run + std::optional task = readyTasks_.tryPop(); + if (!task.has_value()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + // run it + getCurrentTask() = &*task; + impl::gCurrentTaskState = task->task->sharedState(); + tickTask(*task); + getCurrentTask() = nullptr; + impl::gCurrentTaskState = nullptr; + + // and give it back + returningTasks_.push(std::move(*task)); + } +} + +template typename TAllocator> +void BaseMultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) MIJIN_NOEXCEPT +{ + if (&otherLoop == this) { + return; + } + + MIJIN_ASSERT_FATAL(getCurrentTask() != nullptr, "Trying to call transferCurrentTask() while not running a task!"); + + // now start the transfer, first disown the task + StoredTask storedTask = std::move(*getCurrentTask()); + getCurrentTask()->task = nullptr; // just to be sure + + // then send it over to the other loop + otherLoop.addStoredTask(std::move(storedTask)); +} + +template typename TAllocator> +void BaseMultiThreadedTaskLoop::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)); +} + +template typename TAllocator> +void BaseMultiThreadedTaskLoop::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); }); + } +} + +template typename TAllocator> +void BaseMultiThreadedTaskLoop::stop() +{ + workerThreads_.clear(); // will also set the stop token + managerThread_ = {}; // this too +} + // utility stuff inline TaskAwaitableSuspend c_suspend() { @@ -869,10 +1265,40 @@ Task<> c_allDone(const TCollection, TTemplateArgs...>& futures) } while (!allDone); } + +template typename TAllocator, typename... TResult> +struct AllDoneHelper +{ + TaskLoop& currentTaskLoop; + + template + auto makeFuture(TaskBase&& task, std::array& outHandles) + { + return currentTaskLoop.addTaskImpl(std::move(task), &outHandles[index]); + } + + template + auto makeFutures(TaskBase&&... tasks, std::array& outHandles, std::index_sequence) + { + return std::make_tuple(makeFuture(std::move(tasks), outHandles)...); + } +}; + +template typename TAllocator, typename... TResult> +TaskBase, TAllocator> c_allDone(TaskBase&&... tasks) +{ + TaskLoop& currentTaskLoop = TaskLoop::current(); + std::tuple futures = std::make_tuple(currentTaskLoop.addTaskImpl(std::move(tasks), nullptr)...); + while (!allReady(futures)) { + co_await c_suspend(); + } + co_return getAll(futures); +} + [[nodiscard]] inline TaskHandle getCurrentTask() MIJIN_NOEXCEPT { - MIJIN_ASSERT(impl::gCurrentTask != nullptr, "Attempt to call getCurrentTask() outside of task."); - return TaskHandle(impl::gCurrentTask->task->sharedState()); + MIJIN_ASSERT(impl::gCurrentTaskState != nullptr, "Attempt to call getCurrentTask() outside of task."); + return TaskHandle(impl::gCurrentTaskState); } } diff --git a/source/mijin/async/future.hpp b/source/mijin/async/future.hpp index ec59bbc..ac156f9 100644 --- a/source/mijin/async/future.hpp +++ b/source/mijin/async/future.hpp @@ -4,8 +4,9 @@ #if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) #define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1 -#include #include +#include +#include #include #include "./signal.hpp" #include "../container/optional.hpp" @@ -26,7 +27,7 @@ namespace mijin // // public types // -template +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> class Future; // TODO: add support for mutexes and waiting for futures @@ -57,16 +58,17 @@ struct FutureStorage }; } // namespace impl -template +template typename TAllocator> class Future { private: - impl::FutureStorage value_; + [[no_unique_address]] impl::FutureStorage value_; bool isSet_ = false; public: Future() = default; Future(const Future&) = delete; Future(Future&&) MIJIN_NOEXCEPT = default; + explicit Future(TAllocator allocator) : sigSet(std::move(allocator)) {} public: Future& operator=(const Future&) = delete; Future& operator=(Future&&) MIJIN_NOEXCEPT = default; @@ -126,16 +128,52 @@ public: // modification } } public: // signals - Signal<> sigSet; + BaseSignal sigSet; }; -template -using FuturePtr = std::shared_ptr>; +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +using FuturePtr = std::shared_ptr>; // // public functions // +namespace impl +{ +template +struct MultiFutureHelper +{ + template + static bool allReady(const std::tuple...>& futures, std::index_sequence) MIJIN_NOEXCEPT + { + return (std::get(futures)->ready() && ...); + } + + template + static std::tuple...> getAll(const std::tuple...>& futures, std::index_sequence) MIJIN_NOEXCEPT + { + return std::make_tuple(std::move(std::get(futures)->get())...); + } +}; +} + +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +constexpr FuturePtr makeSharedFuture(TAllocator> allocator = {}) MIJIN_NOEXCEPT +{ + return std::allocate_shared>(std::move(allocator)); +} + +template +constexpr bool allReady(const std::tuple...>& futures) MIJIN_NOEXCEPT +{ + return impl::MultiFutureHelper::allReady(futures, std::index_sequence_for()); +} + +template +constexpr std::tuple...> getAll(const std::tuple...>& futures) MIJIN_NOEXCEPT +{ + return impl::MultiFutureHelper::getAll(futures, std::index_sequence_for()); +} } // namespace mijin #endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) diff --git a/source/mijin/async/message_queue.hpp b/source/mijin/async/message_queue.hpp index 67a3cc0..d6e04cc 100644 --- a/source/mijin/async/message_queue.hpp +++ b/source/mijin/async/message_queue.hpp @@ -79,15 +79,20 @@ class MessageQueue public: using message_t = TMessage; using iterator_t = MessageQueueIterator>; + static constexpr std::size_t BUFFER_SIZE = bufferSize; private: - std::array messages; - mijin::BitArray messageReady; - std::atomic_uint writePos = 0; - std::atomic_uint readPos = 0; + std::array messages_; + mijin::BitArray 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& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) + : messages_(messages) {} + explicit MessageQueue(std::array&& messages) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : messages_(std::move(messages)) {} MessageQueue& operator=(const MessageQueue&) = delete; MessageQueue& operator=(MessageQueue&&) = delete; @@ -118,22 +123,22 @@ struct TaskMessageQueue template bool MessageQueue::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::push(TMessage message) template std::optional MessageQueue::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; } diff --git a/source/mijin/async/signal.hpp b/source/mijin/async/signal.hpp index 632b65a..9152c0f 100644 --- a/source/mijin/async/signal.hpp +++ b/source/mijin/async/signal.hpp @@ -33,11 +33,11 @@ inline constexpr signal_token_t INVALID_SIGNAL_TOKEN = std::numeric_limits -class Signal +template typename TAllocator, typename... TArgs> +class BaseSignal { public: - using handler_t = std::function; + using handler_t = std::function; // 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; + using handler_vector_t = std::vector>; 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 allocator = {}) : handlers_(TAllocator(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 inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr referenced = std::weak_ptr()) MIJIN_NOEXCEPT; template inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO, std::weak_ptr referenced = std::weak_ptr()) MIJIN_NOEXCEPT; + template + inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot = Oneshot::NO, std::weak_ptr referenced = std::weak_ptr()) MIJIN_NOEXCEPT; inline void disconnect(token_t token) MIJIN_NOEXCEPT; template inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT; }; +template +using Signal = BaseSignal; + // // public functions // -template +template typename TAllocator, typename... TArgs> template -inline auto Signal::connect(THandler handler, Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t +inline auto BaseSignal::connect(THandler handler, Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t { std::lock_guard lock(handlersMutex_); @@ -91,9 +96,9 @@ inline auto Signal::connect(THandler handler, Oneshot oneshot, std::we return nextToken++; } -template +template typename TAllocator, typename... TArgs> template -inline auto Signal::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t +inline auto BaseSignal::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t { std::lock_guard lock(handlersMutex_); @@ -111,8 +116,28 @@ inline auto Signal::connect(TObject& object, void (TObject::* handler) return nextToken++; } -template -inline void Signal::disconnect(token_t token) MIJIN_NOEXCEPT +template typename TAllocator, typename... TArgs> +template +inline auto BaseSignal::connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t +{ + std::lock_guard lock(handlersMutex_); + + auto callable = [object = &object, handler](TArgs... args) + { + std::invoke(handler, object, std::forward(args)...); + }; + handlers_.push_back({ + .callable = std::move(callable), + .referenced = std::move(referenced), + .token = nextToken, + .oneshot = oneshot + }); + + return nextToken++; +} + +template typename TAllocator, typename... TArgs> +inline void BaseSignal::disconnect(token_t token) MIJIN_NOEXCEPT { std::lock_guard lock(handlersMutex_); @@ -123,9 +148,9 @@ inline void Signal::disconnect(token_t token) MIJIN_NOEXCEPT handlers_.erase(it, handlers_.end()); } -template +template typename TAllocator, typename... TArgs> template -inline void Signal::emit(TArgs2&&... args) MIJIN_NOEXCEPT +inline void BaseSignal::emit(TArgs2&&... args) MIJIN_NOEXCEPT { std::lock_guard lock(handlersMutex_); diff --git a/source/mijin/container/boxed_object.hpp b/source/mijin/container/boxed_object.hpp index c4a9d26..f9365b7 100644 --- a/source/mijin/container/boxed_object.hpp +++ b/source/mijin/container/boxed_object.hpp @@ -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 \ +decltype(auto) funcname(TFuncArgs&&... args) \ +{ \ + return base_t::get().funcname(std::forward(args)...); \ +} + +#define MIJIN_BOXED_PROXY_FUNC_CONST(funcname) \ +template \ +decltype(auto) funcname(TFuncArgs&&... args) const \ +{ \ + return base_t::get().funcname(std::forward(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) #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 - void construct(TArgs&&... args) + constexpr void construct(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) { #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(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) { #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) { #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* end_ = nullptr; #endif public: - BoxedObjectIterator() = default; - explicit constexpr BoxedObjectIterator(BoxedObject* box, BoxedObject* start, BoxedObject* end) + BoxedObjectIterator() noexcept = default; + explicit constexpr BoxedObjectIterator(BoxedObject* box, BoxedObject* start, BoxedObject* 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 -inline BoxedObjectIterator operator+(std::iter_difference_t diff, const BoxedObjectIterator& iter) { +constexpr BoxedObjectIterator operator+(std::iter_difference_t diff, const BoxedObjectIterator& iter) MIJIN_NOEXCEPT { return iter + diff; } static_assert(std::random_access_iterator>); @@ -172,7 +190,7 @@ static_assert(std::random_access_iterator>); // template -BoxedObjectIterator& BoxedObjectIterator::operator+=(difference_type diff) +constexpr BoxedObjectIterator& BoxedObjectIterator::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& BoxedObjectIterator::operator+=(difference_type diff) } template -T& BoxedObjectIterator::operator*() const +constexpr T& BoxedObjectIterator::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::operator*() const } template -BoxedObjectIterator& BoxedObjectIterator::operator++() +constexpr BoxedObjectIterator& BoxedObjectIterator::operator++() MIJIN_NOEXCEPT { #if MIJIN_CHECKED_ITERATORS MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end."); @@ -200,7 +218,7 @@ BoxedObjectIterator& BoxedObjectIterator::operator++() } template -BoxedObjectIterator BoxedObjectIterator::operator++(int) +constexpr BoxedObjectIterator BoxedObjectIterator::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 BoxedObjectIterator::operator++(int) } template -BoxedObjectIterator& BoxedObjectIterator::operator--() +constexpr BoxedObjectIterator& BoxedObjectIterator::operator--() MIJIN_NOEXCEPT { #if MIJIN_CHECKED_ITERATORS MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start."); @@ -220,7 +238,7 @@ BoxedObjectIterator& BoxedObjectIterator::operator--() } template -BoxedObjectIterator BoxedObjectIterator::operator--(int) +constexpr BoxedObjectIterator BoxedObjectIterator::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 BoxedObjectIterator::operator--(int) } template -BoxedObjectIterator BoxedObjectIterator::operator+(difference_type diff) const +constexpr BoxedObjectIterator BoxedObjectIterator::operator+(difference_type diff) const MIJIN_NOEXCEPT { BoxedObjectIterator copy(*this); copy += diff; diff --git a/source/mijin/container/memory_view.hpp b/source/mijin/container/memory_view.hpp index 09ee688..570aa77 100644 --- a/source/mijin/container/memory_view.hpp +++ b/source/mijin/container/memory_view.hpp @@ -5,10 +5,10 @@ #define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1 #include -#include #include +#include #include "../debug/assert.hpp" -#include "../util/traits.hpp" +#include "../internal/common.hpp" namespace mijin { @@ -25,60 +25,172 @@ namespace mijin // public types // -template -class MemoryViewBase +template +concept MemoryViewable = requires(const T& object) +{ + { object.data() } -> std::convertible_to; + { object.byteSize() } -> std::convertible_to; +}; + +template +concept RWMemoryViewable = MemoryViewable && requires(T& object) +{ + { object.data() } -> std::convertible_to; +}; + +template +class MixinMemoryView +{ +public: + static constexpr bool WRITABLE = requires(TConcrete& object) { { object.data() } -> std::convertible_to; }; + + template + [[nodiscard]] + auto makeSpan(); + + template + [[nodiscard]] + auto makeSpan() const; + + template> + [[nodiscard]] + std::basic_string_view makeStringView() const ; + + template + [[nodiscard]] + auto& dataAt(std::size_t offset) MIJIN_NOEXCEPT; + + template + [[nodiscard]] + auto& dataAt(std::size_t offset) const MIJIN_NOEXCEPT; + + [[nodiscard]] + auto bytes() MIJIN_NOEXCEPT + { + using return_t = mijin::copy_const_t(this)->data())>, std::byte>; + return static_cast(static_cast(this)->data()); + } + + [[nodiscard]] + auto bytes() const MIJIN_NOEXCEPT + { + using return_t = mijin::copy_const_t(this)->data())>, std::byte>; + return static_cast(static_cast(this)->data()); + } +private: + std::size_t byteSizeImpl() const MIJIN_NOEXCEPT + { + return static_cast(this)->byteSize(); + } +}; + +class MemoryView : public MixinMemoryView { public: using size_type = std::size_t; private: - std::span bytes_; + void* data_ = nullptr; + std::size_t byteSize_ = 0; public: - MemoryViewBase() noexcept = default; - MemoryViewBase(const MemoryViewBase&) noexcept = default; - MemoryViewBase(copy_const_t* data, std::size_t size) noexcept : bytes_(static_cast(data), size) {} + MemoryView() noexcept = default; + MemoryView(const MemoryView&) = default; + MemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {} + template requires(!std::is_const_v) + MemoryView(std::span span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {} + template + 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 - [[nodiscard]] std::span makeSpan(); + [[nodiscard]] + void* data() const MIJIN_NOEXCEPT { return data_; } - template - [[nodiscard]] std::span makeSpan() const; + [[nodiscard]] + size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; } +}; + +class ConstMemoryView : public MixinMemoryView +{ +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 + ConstMemoryView(std::span span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {} + template + 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; -using ConstMemoryView = MemoryViewBase; // // public functions // -template +template template -std::span MemoryViewBase::makeSpan() +auto MixinMemoryView::makeSpan() { - static_assert(!std::is_const_v, "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(bytes_.data()), - std::bit_cast(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; + return std::span{ + std::bit_cast(bytes()), + std::bit_cast(bytes() + byteSizeImpl()) }; } -template +template template -std::span MemoryViewBase::makeSpan() const +auto MixinMemoryView::makeSpan() const { - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(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; + return std::span{ + std::bit_cast(bytes()), + std::bit_cast(bytes() + byteSizeImpl()) }; } + +template +template +std::basic_string_view MixinMemoryView::makeStringView() const +{ + MIJIN_ASSERT(byteSizeImpl() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type."); + return {std::bit_cast(bytes()), byteSizeImpl() / sizeof(TChar)}; +} + +template +template +auto& MixinMemoryView::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; + return *std::bit_cast(bytes().data() + offset); +} + +template +template +auto& MixinMemoryView::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; + return *std::bit_cast(bytes().data() + offset); +} } // namespace mijin #endif // !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED) diff --git a/source/mijin/container/typeless_buffer.hpp b/source/mijin/container/typeless_buffer.hpp index 139d686..c6b8be2 100644 --- a/source/mijin/container/typeless_buffer.hpp +++ b/source/mijin/container/typeless_buffer.hpp @@ -10,7 +10,9 @@ #include #include #include +#include "./memory_view.hpp" #include "../debug/assert.hpp" +#include "../internal/common.hpp" namespace mijin { @@ -27,17 +29,26 @@ namespace mijin // public types // -template +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> class BufferView; -class TypelessBuffer +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +class BaseTypelessBuffer : public MixinMemoryView> { public: using size_type = std::size_t; private: - std::vector bytes_; + std::vector> bytes_; public: - auto operator<=>(const TypelessBuffer&) const noexcept = default; + BaseTypelessBuffer() noexcept = default; + BaseTypelessBuffer(const BaseTypelessBuffer&) = default; + BaseTypelessBuffer(BaseTypelessBuffer&&) = default; + explicit BaseTypelessBuffer(TAllocator 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 [[nodiscard]] BufferView makeBufferView() { return BufferView(this); } - - template - [[nodiscard]] std::span makeSpan(); - - template - [[nodiscard]] std::span makeSpan() const; - - template> - [[nodiscard]] std::basic_string_view makeStringView() const ; template void append(std::span data); - - template - [[nodiscard]] T& dataAt(size_type offset) MIJIN_NOEXCEPT; - - template - [[nodiscard]] const T& dataAt(size_type offset) const MIJIN_NOEXCEPT; }; -template +using TypelessBuffer = BaseTypelessBuffer<>; + +template 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* buffer_ = nullptr; public: BufferView() = default; - explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {} + explicit BufferView(class BaseTypelessBuffer* buffer) : buffer_(buffer) {} BufferView(const BufferView&) = default; BufferView& operator=(const BufferView&) = default; @@ -131,57 +129,13 @@ public: // public functions // +template typename TAllocator> template -std::span TypelessBuffer::makeSpan() -{ - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(bytes_.data() + bytes_.size()) - }; -} - -template -std::span TypelessBuffer::makeSpan() const -{ - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(bytes_.data() + bytes_.size()) - }; -} - -template -std::basic_string_view TypelessBuffer::makeStringView() const -{ - MIJIN_ASSERT(bytes_.size() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type."); - return {std::bit_cast(bytes_.data()), bytes_.size() / sizeof(TChar)}; -} - -template -void TypelessBuffer::append(std::span data) +void BaseTypelessBuffer::append(std::span data) { bytes_.resize(bytes_.size() + data.size_bytes()); std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes()); } - -template -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(bytes_.data() + offset); -} - -template -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(bytes_.data() + offset); -} } // namespace mijin #endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED) diff --git a/source/mijin/container/vector_map.hpp b/source/mijin/container/vector_map.hpp index a675e48..53e5fb3 100644 --- a/source/mijin/container/vector_map.hpp +++ b/source/mijin/container/vector_map.hpp @@ -7,6 +7,7 @@ #include #include #include + #include "./boxed_object.hpp" #include "./optional.hpp" @@ -102,7 +103,7 @@ public: } }; -template, typename TValueAllocator = std::allocator> +template, typename TValueAllocator = MIJIN_DEFAULT_ALLOCATOR> class VectorMap { public: @@ -119,12 +120,17 @@ private: std::vector keys_; std::vector values_; public: - VectorMap() noexcept = default; + explicit VectorMap(TKeyAllocator keyAllocator = {}) + MIJIN_NOEXCEPT_IF((std::is_nothrow_move_constructible_v && std::is_nothrow_constructible_v)) + : keys_(std::move(keyAllocator)), values_(TValueAllocator(keys_.get_allocator())) {} + VectorMap(TKeyAllocator keyAllocator, TValueAllocator valueAllocator) + MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) + : 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) diff --git a/source/mijin/debug/assert.hpp b/source/mijin/debug/assert.hpp index efc5263..a422398 100644 --- a/source/mijin/debug/assert.hpp +++ b/source/mijin/debug/assert.hpp @@ -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(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 // diff --git a/source/mijin/debug/stacktrace.cpp b/source/mijin/debug/stacktrace.cpp index 567e1e7..777195a 100644 --- a/source/mijin/debug/stacktrace.cpp +++ b/source/mijin/debug/stacktrace.cpp @@ -4,15 +4,24 @@ #include #include #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 + #include +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS + #include + #include + #include + #include + #include + #include "../util/winundef.hpp" + #pragma comment(lib, "dbghelp") #endif @@ -32,11 +41,15 @@ namespace // internal types // +#if MIJIN_USE_LIBBACKTRACE struct BacktraceData { std::optional error; std::vector stackframes; }; +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS +HANDLE gProcessHandle = nullptr; +#endif // // internal variables @@ -44,6 +57,11 @@ struct BacktraceData thread_local Optional 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; + } + + 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 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 symbolBuffer alignas(SYMBOL_INFO); + SYMBOL_INFO& symbolInfo = *std::bit_cast(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 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(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(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 } diff --git a/source/mijin/debug/stacktrace.hpp b/source/mijin/debug/stacktrace.hpp index ac7e485..536ab72 100644 --- a/source/mijin/debug/stacktrace.hpp +++ b/source/mijin/debug/stacktrace.hpp @@ -5,6 +5,7 @@ #define MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED 1 #include +#include #include #include #if __has_include() @@ -87,6 +88,68 @@ TStream& operator<<(TStream& stream, const Stacktrace& stacktrace) } // namespace mijin +template +struct std::formatter +{ + using char_t = TChar; + + template + 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 + 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 +struct std::formatter +{ + using char_t = TChar; + + template + 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 + TContext::iterator format(const mijin::Stacktrace& stacktrace, TContext& ctx) const + { + const int numDigits = static_cast(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() template<> struct fmt::formatter diff --git a/source/mijin/internal/common.hpp b/source/mijin/internal/common.hpp index 57e2266..26a2a30 100644 --- a/source/mijin/internal/common.hpp +++ b/source/mijin/internal/common.hpp @@ -1,4 +1,7 @@ #pragma once +#include "./config.hpp" +#include "./helpers.hpp" #include "./exception.hpp" +#include "./version_support.hpp" diff --git a/source/mijin/internal/config.hpp b/source/mijin/internal/config.hpp new file mode 100644 index 0000000..8a9c054 --- /dev/null +++ b/source/mijin/internal/config.hpp @@ -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) diff --git a/source/mijin/internal/exception.hpp b/source/mijin/internal/exception.hpp index cbccf00..d781d93 100644 --- a/source/mijin/internal/exception.hpp +++ b/source/mijin/internal/exception.hpp @@ -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 diff --git a/source/mijin/internal/helpers.hpp b/source/mijin/internal/helpers.hpp new file mode 100644 index 0000000..1f16253 --- /dev/null +++ b/source/mijin/internal/helpers.hpp @@ -0,0 +1,57 @@ + +#pragma once + +#if !defined(MIJIN_INTERNAL_HELPERS_HPP_INCLUDED) +#define MIJIN_INTERNAL_HELPERS_HPP_INCLUDED 1 + +#include +#include "../util/traits.hpp" + +#define MIJIN_IDENTITY(what) what +#define MIJIN_NULLIFY(what) + +#define MIJIN_SMART_QUOTE(chr_type, text) \ +[](TChar__) consteval \ +{ \ + if constexpr (std::is_same_v) \ + { \ + return text; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + return L ## text; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + return u8 ## text; \ + } \ + else \ + { \ + static_assert(::mijin::always_false_v, "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 ; \ + \ +prefix_a prefix_b(wchar_t) prefix_c \ +using W ## type_name = Base ## type_name ; \ + \ +prefix_a prefix_b(char8_t) prefix_c \ +using U ## type_name = Base ## type_name ; \ + \ +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) diff --git a/source/mijin/internal/version_support.hpp b/source/mijin/internal/version_support.hpp new file mode 100644 index 0000000..5043880 --- /dev/null +++ b/source/mijin/internal/version_support.hpp @@ -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) diff --git a/source/mijin/io/stream.cpp b/source/mijin/io/stream.cpp index 5dd394a..dc7856e 100644 --- a/source/mijin/io/stream.cpp +++ b/source/mijin/io/stream.cpp @@ -197,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 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 bufferBytes = outBuffer.makeSpan(); - std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); - } - return StreamError::SUCCESS; -} - -mijin::Task 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 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 bufferBytes = outBuffer.makeSpan(); - std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); - } - co_return StreamError::SUCCESS; -} - StreamError Stream::readLine(std::string& outString) { MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking."); diff --git a/source/mijin/io/stream.hpp b/source/mijin/io/stream.hpp index b7fa58f..6ea23db 100644 --- a/source/mijin/io/stream.hpp +++ b/source/mijin/io/stream.hpp @@ -132,8 +132,9 @@ public: const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t); return readRaw(&*range.begin(), bytes, {.partial = partial}, outBytesRead); } - - StreamError readRaw(TypelessBuffer& buffer, const ReadOptions& options = {}) + + template typename TAllocator> + StreamError readRaw(BaseTypelessBuffer& 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); return c_readRaw(&*range.begin(), bytes, options, outBytesRead); } - - mijin::Task c_readRaw(TypelessBuffer& buffer, const ReadOptions& options = {}) + + template typename TAllocator> + mijin::Task c_readRaw(BaseTypelessBuffer& buffer, const ReadOptions& options = {}) { return c_readRaw(buffer.data(), buffer.byteSize(), options); } @@ -272,17 +274,21 @@ public: inline StreamError writeString(std::string_view str) { return writeBinaryString(str); } StreamError getTotalLength(std::size_t& outLength); - StreamError readRest(TypelessBuffer& outBuffer); - mijin::Task c_readRest(TypelessBuffer& outBuffer); + + template typename TAllocator> + StreamError readRest(BaseTypelessBuffer& outBuffer); + + template typename TAllocator> + mijin::Task c_readRest(BaseTypelessBuffer& outBuffer); StreamError readLine(std::string& outString); mijin::Task c_readLine(std::string& outString); - template - StreamError readAsString(std::basic_string& outString); + template, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> + StreamError readAsString(std::basic_string& outString); - template - mijin::Task c_readAsString(std::basic_string& outString); + template, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> + mijin::Task c_readAsString(std::basic_string& outString); StreamError writeText(std::string_view str) { @@ -363,8 +369,80 @@ using StreamResult = ResultBase; // public functions // -template -StreamError Stream::readAsString(std::basic_string& outString) +template typename TAllocator> +StreamError Stream::readRest(BaseTypelessBuffer& 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 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 bufferBytes = outBuffer.template makeSpan(); + std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); + } + return StreamError::SUCCESS; +} + +template typename TAllocator> +mijin::Task Stream::c_readRest(BaseTypelessBuffer& 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 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 bufferBytes = outBuffer.template makeSpan(); + std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); + } + co_return StreamError::SUCCESS; +} + +template +StreamError Stream::readAsString(std::basic_string& outString) { static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t"); @@ -399,8 +477,8 @@ StreamError Stream::readAsString(std::basic_string& outString) return StreamError::SUCCESS; } -template -mijin::Task Stream::c_readAsString(std::basic_string& outString) +template +mijin::Task Stream::c_readAsString(std::basic_string& outString) { static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t"); diff --git a/source/mijin/logging/formatting.hpp b/source/mijin/logging/formatting.hpp new file mode 100644 index 0000000..ad3a6f8 --- /dev/null +++ b/source/mijin/logging/formatting.hpp @@ -0,0 +1,323 @@ + +#pragma once + +#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED) +#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1 + +#include +#include +#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 TAllocator = MIJIN_DEFAULT_ALLOCATOR +template, + 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; + + virtual ~BaseLogFormatter() noexcept = default; + + virtual void format(const LogMessage& message, string_t& outFormatted) = 0; +}; + +template, + FORMATTER_COMMON_ARGS(TChar)> +class BaseSimpleLogFormatter : public BaseLogFormatter +{ +public: + using char_t = TChar; + using traits_t = TTraits; + using allocator_t = TAllocator; + using base_t = BaseLogFormatter; + using typename base_t::string_t; + using string_view_t = std::basic_string_view; +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, 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, \ + template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, \ + deleter_type>> TDeleter \ + = AllocatorDeleter>>> + +#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 + requires(allocator_type>) +class BaseFormattingLogSink : public BaseLogSink +{ +public: + using base_t = BaseLogSink; + + using char_t = TChar; + using traits_t = TTraits; + using allocator_t = TAllocator; + using formatter_t = BaseLogFormatter; + using formatter_deleter_t = TDeleter; + using formatter_ptr_t = DynamicPointer; + using string_t = formatter_t::string_t; + using typename base_t::message_t; +private: + not_null_t mFormatter; + string_t mBuffer; +public: + explicit BaseFormattingLogSink(not_null_t formatter, allocator_t allocator = {}) + MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : 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, TAllocator, TDeleter + +MIJIN_DEFINE_CHAR_VERSIONS_TMPL(FormattingLogSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS) + +#undef SINK_SET_ARGS + +template TAllocator> +void BaseSimpleLogFormatter::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; + + // if there is no format, just directly print the value + if (argFormat.empty()) + { + if constexpr (is_char_v) + { + convertStringType(string_view_t(&value, 1), outFormatted); + } + else if constexpr (std::is_arithmetic_v) + { + std::format_to(std::back_inserter(outFormatted), MIJIN_SMART_QUOTE(char_t, "{}"), value); + } + else if constexpr (is_string_v || is_cstring_v) + { + convertStringType(value, outFormatted); + } + else + { + static_assert(always_false_v); + 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) + { + std::vformat_to(std::back_inserter(string), format, std::make_format_args(value)); + } + else if constexpr (std::is_same_v) + { + std::vformat_to(std::back_inserter(string), format, std::make_wformat_args(value)); + } + else + { + static_assert(always_false_v, "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 && !std::is_same_v) + { + static_assert(always_false_v, "TODO..."); + } + else if constexpr ((is_string_v || is_cstring_v) && !std::is_same_v, 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 TAllocator> +void BaseSimpleLogFormatter::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::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::FG_CYAN; + } + else if (levelValue < MIJIN_LOG_LEVEL_VALUE_INFO) + { + outFormatted += BaseAnsiFontEffects::FG_WHITE; + } + else if (levelValue < MIJIN_LOG_LEVEL_VALUE_WARNING) + { + outFormatted += BaseAnsiFontEffects::FG_BRIGHT_WHITE; + } + else if (levelValue < MIJIN_LOG_LEVEL_VALUE_ERROR) + { + outFormatted += BaseAnsiFontEffects::FG_YELLOW; + } + else + { + outFormatted += BaseAnsiFontEffects::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) diff --git a/source/mijin/logging/logger.hpp b/source/mijin/logging/logger.hpp new file mode 100644 index 0000000..d2db264 --- /dev/null +++ b/source/mijin/logging/logger.hpp @@ -0,0 +1,256 @@ + +#pragma once + +#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED) +#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#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 +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 +struct BaseLogChannel +{ + using char_t = TChar; + + const char_t* name; +}; + +MIJIN_DEFINE_CHAR_VERSIONS(LogChannel) + +template +struct BaseLogMessage +{ + using char_t = TChar; + + const char_t* text; + const BaseLogChannel* channel; + const BaseLogLevel* level; + std::source_location sourceLocation; +}; + +MIJIN_DEFINE_CHAR_VERSIONS(LogMessage) + +template +class BaseLogSink +{ +public: + using char_t = TChar; + using message_t = BaseLogMessage; + + virtual ~BaseLogSink() noexcept = default; + + virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0; +}; + +MIJIN_DEFINE_CHAR_VERSIONS(LogSink) + +#define LOGGER_COMMON_ARGS(chr_type) template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR +template, LOGGER_COMMON_ARGS(TChar)> +class BaseLogger +{ +public: + using char_t = TChar; + using traits_t = TTraits; + using allocator_t = TAllocator; + + using sink_t = BaseLogSink; + using level_t = BaseLogLevel; + using channel_t = BaseLogChannel; + using message_t = BaseLogMessage; + using string_t = std::basic_string; +private: + std::vector> mSinks; +public: + explicit BaseLogger(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : mSinks(std::move(allocator)) + {} + + BaseLogger(const BaseLogger&) = default; + + BaseLogger(BaseLogger&&) = default; + + BaseLogger& operator=(const BaseLogger&) = default; + + BaseLogger& operator=(BaseLogger&&) = default; + + void addSink(sink_t& sink) + { + mSinks.push_back(&sink); + } + + void postMessage(const message_t& message) const MIJIN_NOEXCEPT + { + for (sink_t* sink: mSinks) + { + 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 + void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, + std::basic_format_string...> fmt, TArgs&& ... args) const + MIJIN_NOEXCEPT_IF(noexcept(std::declval().allocate(1))) + { + string_t buffer(allocator_t(mSinks.get_allocator())); + std::format_to(std::back_inserter(buffer), fmt, std::forward(args)...); + log(level, channel, std::move(sourceLocation), buffer.c_str()); + } +}; + +#define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits, 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 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 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 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(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_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_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) diff --git a/source/mijin/logging/stdio_sink.hpp b/source/mijin/logging/stdio_sink.hpp new file mode 100644 index 0000000..7aa1e8c --- /dev/null +++ b/source/mijin/logging/stdio_sink.hpp @@ -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 + requires(allocator_type>) +class BaseStdioSink : public BaseFormattingLogSink +{ +public: + using base_t = BaseFormattingLogSink; + 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(not_null_t formatter, allocator_t allocator = {}) + MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : 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& 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) + { + std::fputs(formatted, stream); + std::fputc('\n', stream); + } + else if constexpr (std::is_same_v) + { + 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(formatted), stream); + std::fputc('\n', stream); + } + else + { + static_assert(always_false_v, "Character type not supported."); + } + std::fflush(stream); + } +}; + +#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits, 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) diff --git a/source/mijin/memory/dynamic_pointer.hpp b/source/mijin/memory/dynamic_pointer.hpp new file mode 100644 index 0000000..1edd64a --- /dev/null +++ b/source/mijin/memory/dynamic_pointer.hpp @@ -0,0 +1,176 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED) +#define MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED 1 + +#include +#include +#include + +#include "../internal/common.hpp" +#include "../memory/memutil.hpp" +#include "../util/concepts.hpp" +#include "../util/flag.hpp" + +namespace mijin +{ +MIJIN_DEFINE_FLAG(Owning); + +template TDeleter = std::default_delete> +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) + : mData(std::bit_cast(ptr) | (owning ? 1 : 0)), mDeleter(std::move(deleter)) + { + MIJIN_ASSERT((std::bit_cast(ptr) & 1) == 0, "Invalid address, DynamicPointer requires addresses to be divisible by two."); + } + template requires (std::is_constructible_v) + constexpr DynamicPointer(DynamicPointer&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_convertible_v)) + : mData(std::exchange(other.mData, 0)), mDeleter(std::move(other.mDeleter)) { + MIJIN_ASSERT(other.mData == 0, ""); + } + constexpr ~DynamicPointer() noexcept + { + reset(); + } + + DynamicPointer& operator=(const DynamicPointer&) = delete; + DynamicPointer& operator=(std::nullptr_t) MIJIN_NOEXCEPT + { + reset(); + return *this; + } + + template requires(std::is_assignable_v) + DynamicPointer& operator=(DynamicPointer&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v)) + { + if (this != &other) + { + reset(); + mData = std::exchange(other.mData, 0); + mDeleter = std::move(other.mDeleter); + } + return *this; + } + + template requires(std::equality_comparable_with) + auto operator<=>(const DynamicPointer& 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(mData & ~1); + } + + constexpr void reset(pointer ptr, Owning owning) MIJIN_NOEXCEPT + { + if (isOwning()) + { + mDeleter(get()); + } + mData = std::bit_cast(ptr) | (owning ? 1 : 0); + } + + constexpr void reset() MIJIN_NOEXCEPT + { + reset(nullptr, Owning::NO); + } + + [[nodiscard]] + pointer release() MIJIN_NOEXCEPT + { + return std::bit_cast(std::exchange(mData, 0) & ~1); + } + + template TOtherDeleter> + friend class DynamicPointer; +}; + +template +bool operator==(std::nullptr_t, const DynamicPointer& pointer) MIJIN_NOEXCEPT +{ + return pointer == nullptr; +} + +template +bool operator!=(std::nullptr_t, const DynamicPointer& pointer) MIJIN_NOEXCEPT +{ + return pointer != nullptr; +} + +template +DynamicPointer> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) +{ + return DynamicPointer>(new T(std::forward(args)...), Owning::YES); +} + +template TAllocator, typename... TArgs> +DynamicPointer> makeDynamicWithAllocator(TAllocator allocator, TArgs&&... args) + MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v && std::is_nothrow_move_constructible_v)) +{ + T* obj = allocator.allocate(1); + if (obj != nullptr) + { + ::new(obj) T(std::forward(args)...); + } + return DynamicPointer>(obj, Owning::YES, AllocatorDeleter(std::move(allocator))); +} +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED) diff --git a/source/mijin/memory/memutil.hpp b/source/mijin/memory/memutil.hpp new file mode 100644 index 0000000..bb4598e --- /dev/null +++ b/source/mijin/memory/memutil.hpp @@ -0,0 +1,90 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED) +#define MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED 1 + +#include +#include "../internal/common.hpp" + +namespace mijin +{ +template +class AllocatorDeleter +{ +public: + using value_type = std::allocator_traits::value_type; + using pointer = std::allocator_traits::pointer; + +private: + [[no_unique_address]] TAllocator allocator_; + +public: + AllocatorDeleter() = default; + explicit AllocatorDeleter(TAllocator allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : allocator_(std::move(allocator)) {} + + template requires (std::is_constructible_v) + AllocatorDeleter(const AllocatorDeleter& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : allocator_(other.allocator_) {} + + template requires (std::is_constructible_v) + AllocatorDeleter(AllocatorDeleter&& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : allocator_(std::move(other.allocator_)) {} + + template requires (std::is_assignable_v) + AllocatorDeleter& operator=(const AllocatorDeleter& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v)) + { + if (this != static_cast(&other)) + { + allocator_ = other.allocator_; + } + return *this; + } + + template requires (std::is_assignable_v) + AllocatorDeleter& operator=(AllocatorDeleter&& other) + MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v)) + { + if (this != static_cast(&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 + friend class AllocatorDeleter; +}; + +template +class AllocatorDeleter> +{ +public: + AllocatorDeleter() noexcept = default; + + template + AllocatorDeleter(std::allocator) noexcept {} + + template + AllocatorDeleter(const AllocatorDeleter>&) noexcept {} + + template + AllocatorDeleter& operator=(const AllocatorDeleter>&) noexcept { return *this; } + + void operator()(T* ptr) const MIJIN_NOEXCEPT + { + delete ptr; + } +}; +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED) diff --git a/source/mijin/memory/stack_allocator.hpp b/source/mijin/memory/stack_allocator.hpp new file mode 100644 index 0000000..745e01a --- /dev/null +++ b/source/mijin/memory/stack_allocator.hpp @@ -0,0 +1,486 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED) +#define MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED 1 + +#include +#include +#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 +#include +#include "../debug/stacktrace.hpp" +#endif + +namespace mijin +{ +template +class StlStackAllocator +{ +public: + using value_type = TValue; +private: + TStackAllocator* base_; +public: + explicit StlStackAllocator(TStackAllocator& base) MIJIN_NOEXCEPT : base_(&base) {} + template + StlStackAllocator(const StlStackAllocator& other) MIJIN_NOEXCEPT : base_(other.base_) {} + + template + StlStackAllocator& operator=(const StlStackAllocator& 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(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 + 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> 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 typename TBacking> requires (allocator_tmpl) + friend class StackAllocator; +}; + +template +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 typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl) +class StackAllocator +{ +public: + using backing_t = TBacking; + static constexpr std::size_t ACTUAL_CHUNK_SIZE = chunkSize - sizeof(void*) - sizeof(std::size_t); + + template + using stl_allocator_t = StlStackAllocator>; +private: + struct Chunk + { + std::array data; + Chunk* next; + std::size_t allocated; + }; + [[no_unique_address]] TBacking 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> activeAllocations_; +#endif +public: + StackAllocator() MIJIN_NOEXCEPT_IF(std::is_nothrow_default_constructible_v) = default; + explicit StackAllocator(backing_t backing) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v, backing_t&&>)) + : backing_(std::move(backing)) {} + StackAllocator(const StackAllocator&) = delete; + StackAllocator(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : 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>) + { + 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 + stl_allocator_t makeStlAllocator() MIJIN_NOEXCEPT + { + return stl_allocator_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(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(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(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 + friend class StlStackAllocator; +}; +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED) diff --git a/source/mijin/memory/virtual_allocator.hpp b/source/mijin/memory/virtual_allocator.hpp new file mode 100644 index 0000000..a0a6ef2 --- /dev/null +++ b/source/mijin/memory/virtual_allocator.hpp @@ -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 +class VirtualAllocator +{ +public: + virtual ~VirtualAllocator() noexcept = default; + + [[nodiscard]] + virtual owner_t allocate(std::size_t count) noexcept; + virtual void deallocate(owner_t ptr, std::size_t count) noexcept; +}; + +template +class WrappedVirtualAllocator : public VirtualAllocator +{ +private: + [[no_unique_address]] TImpl mImpl; +public: + explicit constexpr WrappedVirtualAllocator(TImpl impl = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + : 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 allocate(std::size_t count) noexcept override + { + return mImpl.allocate(count); + } + + void deallocate(owner_t ptr, std::size_t count) noexcept override + { + mImpl.deallocate(ptr, count); + } +}; + +template +WrappedVirtualAllocator makeVirtualAllocator(T allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) +{ + return WrappedVirtualAllocator(std::move(allocator)); +} +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED) diff --git a/source/mijin/net/http.hpp b/source/mijin/net/http.hpp index ebcbf91..beb5424 100644 --- a/source/mijin/net/http.hpp +++ b/source/mijin/net/http.hpp @@ -44,7 +44,7 @@ struct HTTPRequestOptions { std::string method = "GET"; std::multimap headers; - TypelessBuffer body; + BaseTypelessBuffer body; }; struct HTTPResponse @@ -53,7 +53,7 @@ struct HTTPResponse unsigned status; std::string statusMessage; std::multimap headers; - TypelessBuffer body; + BaseTypelessBuffer body; }; class HTTPStream diff --git a/source/mijin/net/url.hpp b/source/mijin/net/url.hpp index 4456a58..8a39fe3 100644 --- a/source/mijin/net/url.hpp +++ b/source/mijin/net/url.hpp @@ -12,7 +12,7 @@ namespace mijin { -template, typename TAllocator = std::allocator> +template, typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> 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; diff --git a/source/mijin/util/align.hpp b/source/mijin/util/align.hpp index 866ef26..c2611db 100644 --- a/source/mijin/util/align.hpp +++ b/source/mijin/util/align.hpp @@ -4,6 +4,8 @@ #if !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED) #define MIJIN_UTIL_ALIGN_HPP_INCLUDED 1 +#include +#include #include "../internal/common.hpp" namespace mijin @@ -18,7 +20,13 @@ constexpr T alignUp(T value, T alignTo) MIJIN_NOEXCEPT return value; } +template +T* alignUp(T* pointer, std::uintptr_t alignTo) MIJIN_NOEXCEPT +{ + return std::bit_cast(alignUp(std::bit_cast(pointer), alignTo)); +} + #define MIJIN_STRIDEOF(T) mijin::alignUp(sizeof(T), alignof(T)) } // namespace mijin -#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED) \ No newline at end of file +#endif // !defined(MIJIN_UTIL_ALIGN_HPP_INCLUDED) diff --git a/source/mijin/util/annot.hpp b/source/mijin/util/annot.hpp new file mode 100644 index 0000000..adc406b --- /dev/null +++ b/source/mijin/util/annot.hpp @@ -0,0 +1,179 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED) +#define MIJIN_UTIL_ANNOT_HPP_INCLUDED 1 + +#include +#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() + #define MIJIN_USE_GSL 1 +#else + #define MIJIN_USE_GSL 0 +#endif +#endif // !defined(MIJIN_USE_GSL) + +#include +#include "./concepts.hpp" + +#if MIJIN_USE_GSL + #include +#endif + +namespace mijin +{ +template +concept nullable_type = !std::is_same_v && requires(T t) { t == nullptr; }; + +template +class NotNullable +{ +private: + T base_; +public: + template requires(std::is_same_v && std::is_copy_constructible_v) + constexpr NotNullable(U base) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) + : 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) = default; + NotNullable(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) = default; + + template requires(std::is_constructible_v) + constexpr NotNullable(const NotNullable& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : base_(other.base_) + { + MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr."); + } + template requires(std::is_constructible_v) + constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : base_(std::exchange(other.base_, nullptr)) + { + MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr."); + } + template requires(!std::is_same_v + && (!std::is_same_v && sizeof...(TArgs) == 0) + && std::is_constructible_v) + constexpr NotNullable(TArg&& arg, TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v)) + : base_(std::forward(arg), std::forward(args)...) + { + MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr."); + } + constexpr NotNullable(T&& base) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) + requires(std::is_move_constructible_v) + : 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) + requires(std::is_copy_constructible_v) + : 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) + requires(std::is_move_constructible_v) + : 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) = default; + NotNullable& operator=(NotNullable&&) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) = default; + + constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_assignable_v) + requires(std::is_copy_assignable_v) + { + 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) + requires(std::is_move_assignable_v) + { + 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 TOther> + bool operator==(const NotNullable& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() == std::declval())) + { + return base_ == other.base_; + } + + template TOther> + bool operator!=(const NotNullable& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() != std::declval())) + { + return base_ != other.base_; + } + + template requires(std::equality_comparable_with && !std::is_same_v) + bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() == std::declval())) + { + return base_ == other; + } + + template requires(std::equality_comparable_with && !std::is_same_v) + bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval() != std::declval())) + { + 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_; } + + template + friend class NotNullable; +}; + +#if MIJIN_USE_GSL +template +using owner_t = gsl::owner; +#else +template +using owner_t = T; +#endif + +template +using not_null_t = NotNullable; +} + +#endif // !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED) diff --git a/source/mijin/util/ansi_colors.hpp b/source/mijin/util/ansi_colors.hpp new file mode 100644 index 0000000..6c607dc --- /dev/null +++ b/source/mijin/util/ansi_colors.hpp @@ -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 +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) diff --git a/source/mijin/util/concepts.hpp b/source/mijin/util/concepts.hpp index e40c4a1..a985a12 100644 --- a/source/mijin/util/concepts.hpp +++ b/source/mijin/util/concepts.hpp @@ -5,6 +5,7 @@ #define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1 #include +#include "./traits.hpp" namespace mijin { @@ -39,6 +40,32 @@ concept pointer_type = std::is_pointer_v; template concept reference_type = std::is_reference_v; +namespace impl +{ +template +using pointer_t = typename T::pointer; +} + +template +concept allocator_type = requires(T alloc, typename T::value_type value, detect_or_t pointer, int count) +{ + typename T::value_type; + { alloc.allocate(count) } -> std::same_as; + { alloc.deallocate(pointer, count) } -> std::same_as; +} && !std::is_const_v && !std::is_volatile_v; + +template +concept allocator_type_for = allocator_type && std::is_same_v; + +template typename T> +concept allocator_tmpl = allocator_type>; + +template +concept deleter_type = requires(T deleter, TData* ptr) +{ + deleter(ptr); +}; + // // public functions // diff --git a/source/mijin/util/hash.hpp b/source/mijin/util/hash.hpp index 9ebe40b..7b359bb 100644 --- a/source/mijin/util/hash.hpp +++ b/source/mijin/util/hash.hpp @@ -6,6 +6,8 @@ #define MIJIN_UTIL_HASH_HPP_INCLUDED 1 #include +#include +#include namespace mijin { @@ -16,4 +18,21 @@ inline void hashCombine(std::size_t& seed, const T& value, const THasher& hasher } } +template +struct std::hash> +{ + std::size_t operator()(const std::tuple& tuple) const noexcept + { + return hashImpl(tuple, std::index_sequence_for()); + } + + template + std::size_t hashImpl(const std::tuple& tuple, std::index_sequence) const noexcept + { + std::size_t result = 0; + (mijin::hashCombine(result, std::get(tuple)), ...); + return result; + } +}; + #endif // MIJIN_UTIL_HASH_HPP_INCLUDED diff --git a/source/mijin/util/misc.hpp b/source/mijin/util/misc.hpp new file mode 100644 index 0000000..e8bb82d --- /dev/null +++ b/source/mijin/util/misc.hpp @@ -0,0 +1,42 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_MISC_HPP_INCLUDED) +#define MIJIN_UTIL_MISC_HPP_INCLUDED 1 + +#include +#include + +namespace mijin +{ + +// +// public functions +// + +template +constexpr decltype(auto) idValue(T&& value) +{ + return std::forward(value); +} + +namespace impl +{ +template +struct ConstructArrayHelper +{ + template + static constexpr std::array construct(const TArgs&... args, std::index_sequence) + { + return {idValue(T(args...))...}; + } +}; +} + +template +constexpr std::array constructArray(const TArgs&... args) +{ + return impl::ConstructArrayHelper::construct(args..., std::make_index_sequence()); +} +} +#endif // !defined(MIJIN_UTIL_MISC_HPP_INCLUDED) diff --git a/source/mijin/util/os.cpp b/source/mijin/util/os.cpp index 1d7b7ce..973268e 100644 --- a/source/mijin/util/os.cpp +++ b/source/mijin/util/os.cpp @@ -5,11 +5,14 @@ #include "../debug/assert.hpp" #if MIJIN_TARGET_OS == MIJIN_OS_LINUX + #include + #include #include #include #include #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS #include + #include #include #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(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 diff --git a/source/mijin/util/os.hpp b/source/mijin/util/os.hpp index b071d66..0d10dff 100644 --- a/source/mijin/util/os.hpp +++ b/source/mijin/util/os.hpp @@ -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(); diff --git a/source/mijin/util/string.hpp b/source/mijin/util/string.hpp index 05540d8..33cacad 100644 --- a/source/mijin/util/string.hpp +++ b/source/mijin/util/string.hpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -17,6 +20,7 @@ #include "./iterators.hpp" #include "../internal/common.hpp" +#include "../util/traits.hpp" namespace mijin { @@ -33,8 +37,56 @@ namespace mijin // public traits // -template -using char_type_t = decltype(std::string_view(std::declval()))::value_type; +template +inline constexpr bool is_string_v = is_template_instance_v>; + +template +concept std_string_type = is_string_v; + +template +inline constexpr bool is_string_view_v = is_template_instance_v>; + +template +concept std_string_view_type = is_string_view_v; + +template +inline constexpr bool is_char_v = is_any_type_v, char, wchar_t, char8_t, char16_t, char32_t>; + +template +concept char_type = is_char_v; + +template +inline constexpr bool is_cstring_v = std::is_pointer_v && is_char_v>; + +template +concept cstring_type = is_cstring_v; + +template +struct str_char_type +{ + using type = void; +}; + +template +struct str_char_type +{ + using type = std::remove_cvref_t; +}; + +template +struct str_char_type +{ + using type = typename std::remove_cvref_t::value_type; +}; + +template +struct str_char_type +{ + using type = typename std::remove_cvref_t::value_type; +}; + +template +using str_char_type_t = str_char_type::type; // // public types @@ -255,7 +307,7 @@ template [[nodiscard]] auto trimPrefix(TString&& string) { - return trimPrefix(string, detail::DEFAULT_TRIM_CHARS>); + return trimPrefix(string, detail::DEFAULT_TRIM_CHARS>); } template @@ -269,7 +321,7 @@ template [[nodiscard]] auto trimSuffix(TString&& string) { - return trimSuffix(string, detail::DEFAULT_TRIM_CHARS>); + return trimSuffix(string, detail::DEFAULT_TRIM_CHARS>); } template @@ -283,7 +335,7 @@ template [[nodiscard]] auto trim(TString&& string) { - return trim(string, detail::DEFAULT_TRIM_CHARS>); + return trim(string, detail::DEFAULT_TRIM_CHARS>); } template @@ -425,7 +477,7 @@ struct Join { const char* delimiter; - explicit Join(const char* delimiter_) MIJIN_NOEXCEPT : delimiter(delimiter_) {} + explicit Join(const char* delimiter_) MIJIN_NOEXCEPT: delimiter(delimiter_) {} }; template @@ -433,6 +485,142 @@ auto operator|(TIterable&& iterable, const Join& joiner) { return join(std::forward(iterable), joiner.delimiter); } +} // namespace pipe + +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(*this); + } +}; + +template +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) + { + if constexpr (std::is_same_v) + { + const std::size_t result = std::mbrtowc(outTo, chrFrom, numFrom, &mbstate); + if (result == static_cast(-1)) + { + return {}; + } + return { + .numRead = static_cast(result), + .numWritten = 1 + }; + } + if constexpr (std::is_same_v) + { + + } + } + if constexpr (std::is_same_v) + { + if constexpr (std::is_same_v) + { + 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(-1)) + { + return {}; + } + return { + .numRead = 1, + .numWritten = static_cast(result) + }; + } + if constexpr (std::is_same_v) + { + + } + } + if constexpr (std::is_same_v) + { + + } +} +template +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 +struct [[nodiscard]] ConvertStringTypeResult +{ + TIterator iterator; + bool success; +}; + +template TIterator> +ConvertStringTypeResult convertStringType(std::basic_string_view 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 +bool convertStringType(std::basic_string_view strFrom, std::basic_string& outString) +{ + if constexpr (std::is_same_v) + { + outString += strFrom; + return true; + } + else + { + return convertStringType(strFrom, std::back_inserter(outString)).success; + } +} + +template +bool convertStringType(const TFrom* strFrom, std::basic_string& outString) +{ + return convertStringType(std::basic_string_view(strFrom), outString); } } // namespace mijin diff --git a/source/mijin/util/traits.hpp b/source/mijin/util/traits.hpp index 72a166d..bddd0c0 100644 --- a/source/mijin/util/traits.hpp +++ b/source/mijin/util/traits.hpp @@ -21,6 +21,9 @@ namespace mijin // public types // +struct Type_; // use as typevar +struct Any_; // use as placeholder in templates + template struct always_false { @@ -95,11 +98,88 @@ struct map_template { using type_t = TTemplate>; }; -template -struct is_any_type : std::disjunction...> {}; +template typename TTemplate, typename TType> +struct is_template_instance : std::false_type {}; + +template typename TTemplate, typename... TArgs> +struct is_template_instance> : std::true_type {}; + +template typename TTemplate, typename TType> +constexpr bool is_template_instance_v = is_template_instance::value; + +namespace impl +{ +template typename TTemplate, typename... TArgsTTmpl> +struct tmpl_param_comparator +{ + template + static constexpr bool compare_single = std::is_same_v or std::is_same_v; + + template + struct matches : std::false_type {}; + + // original (which MSVC didn't like for some reason :/) + // template + // struct matches> : std::bool_constant<(compare_single && ...)> {}; + + template typename TOtherTemplate, typename... TArgsInstance> requires(is_template_instance_v>) + struct matches> : std::bool_constant<(compare_single && ...)> {}; + + template + using matches_t = matches; +}; +} + +template +struct match_template_type : std::false_type {}; + +template typename TTemplate, typename TType, typename... TArgs> +struct match_template_type, TType> : impl::tmpl_param_comparator::template matches_t {}; + +template +constexpr bool match_template_type_v = match_template_type::value; + +// similar to std::is_same, but allows placeholders +// - is_type_or_impl, some_type> resolves to some_tmpl +// - is_type_or_impl, some_type> checks if some_type is an instance of some_tmpl with int as 2nd parameter +template +struct type_matches +{ + using type = std::is_same; +}; + +template typename TTmplMatch, typename TConcrete> +struct type_matches, TConcrete> +{ + using type = TTmplMatch; +}; + +template typename TTmplMatch, typename TConcrete, typename... TArgs> +struct type_matches, TConcrete> +{ + using type = match_template_type, TConcrete>; +}; + +template +using type_matches_t = type_matches::type; + +template +inline constexpr bool type_matches_v = type_matches_t::value; + +template +struct is_any_type : std::disjunction...> {}; + +template +static constexpr bool is_any_type_v = is_any_type::value; + +template +struct match_any_type : std::disjunction...> {}; + +template +static constexpr bool match_any_type_v = match_any_type::value; template -static constexpr bool is_any_type_v = is_any_type::value; +concept union_type = match_any_type_v; template struct is_type_member; @@ -139,14 +219,39 @@ struct TypeAtHelper<0, TArg, TArgs...> template using type_at_t = TypeAtHelper::type_t; -template typename TTemplate, typename TType> -struct is_template_instance : std::false_type {}; +template typename TOper, typename... TArgs> +struct detect_or +{ + using type = TDefault; + static constexpr bool detected = false; +}; -template typename TTemplate, typename... TArgs> -struct is_template_instance> : std::true_type {}; +template typename TOper, typename... TArgs> + requires requires { typename TOper; } +struct detect_or +{ + using type = TOper; + static constexpr bool detected = true; +}; +template typename TOper, typename... TArgs> +using detect_or_t = detect_or::type; -template typename TTemplate, typename TType> -constexpr bool is_template_instance_v = is_template_instance::value; +struct empty_type {}; + +template +struct optional_base +{ + using type = T; +}; + +template +struct optional_base +{ + using type = empty_type; +}; + +template +using optional_base_t = optional_base::type; // // public functions @@ -158,6 +263,34 @@ decltype(auto) delayEvaluation(TType&& value) return static_cast(value); } +#define MIJIN_STATIC_TESTS 1 +#if MIJIN_STATIC_TESTS +namespace test +{ +template +struct MyTemplate {}; + +static_assert(match_template_type_v, MyTemplate>); +static_assert(match_template_type_v, MyTemplate>); +static_assert(type_matches_v, MyTemplate>); +static_assert(type_matches_v, MyTemplate>); +static_assert(!type_matches_v, MyTemplate>); +static_assert(type_matches_v, MyTemplate>); +static_assert(type_matches_v, void*>); + +static_assert(union_type); +static_assert(!union_type); +static_assert(!union_type); +static_assert(union_type, MyTemplate>); +static_assert(union_type, MyTemplate>); +static_assert(union_type, MyTemplate>); +static_assert(union_type, MyTemplate, MyTemplate>); +static_assert(!union_type); +static_assert(union_type>); +static_assert(!union_type>); +} +#endif + } // namespace mijin #endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED) diff --git a/source/mijin/util/winundef.hpp b/source/mijin/util/winundef.hpp index 9a155cb..715fc40 100644 --- a/source/mijin/util/winundef.hpp +++ b/source/mijin/util/winundef.hpp @@ -22,3 +22,7 @@ #if defined(RELATIVE) #undef RELATIVE #endif + +#if defined(DEBUG) +#undef DEBUG +#endif diff --git a/source/mijin/virtual_filesystem/memory.cpp b/source/mijin/virtual_filesystem/memory.cpp index 45d27e2..8e1c6fd 100644 --- a/source/mijin/virtual_filesystem/memory.cpp +++ b/source/mijin/virtual_filesystem/memory.cpp @@ -139,7 +139,7 @@ detail::MemoryFolder* MemoryFileSystemAdapter::findFolder(const fs::path& path, return folder; } -FileInfo MemoryFileSystemAdapter::folderInfo(const fs::path& path, const detail::MemoryFolder& folder) const noexcept +FileInfo MemoryFileSystemAdapter::folderInfo(const fs::path& path, const detail::MemoryFolder& folder) const MIJIN_NOEXCEPT { return { .path = path, @@ -149,7 +149,7 @@ FileInfo MemoryFileSystemAdapter::folderInfo(const fs::path& path, const detail: }; } -FileInfo MemoryFileSystemAdapter::fileInfo(const fs::path& path, const detail::MemoryFile& file) const noexcept +FileInfo MemoryFileSystemAdapter::fileInfo(const fs::path& path, const detail::MemoryFile& file) const MIJIN_NOEXCEPT { return { .path = path, diff --git a/source/mijin/virtual_filesystem/memory.hpp b/source/mijin/virtual_filesystem/memory.hpp index b5c465d..c7736bc 100644 --- a/source/mijin/virtual_filesystem/memory.hpp +++ b/source/mijin/virtual_filesystem/memory.hpp @@ -21,8 +21,8 @@ struct MemoryFile }; struct MemoryFolder { - VectorMap files; - VectorMap folders; + VectorMap, std::allocator> files; // TODO: make the FS library allocator aware + VectorMap, std::allocator> folders; }; } diff --git a/source/mijin/virtual_filesystem/stacked.cpp b/source/mijin/virtual_filesystem/stacked.cpp index 2c2eae8..89a33c1 100644 --- a/source/mijin/virtual_filesystem/stacked.cpp +++ b/source/mijin/virtual_filesystem/stacked.cpp @@ -39,10 +39,15 @@ fs::path StackedFileSystemAdapter::getHomeFolder() #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 }