From da781b87f26ee8ba0000bcf72fc60ec8877e06c5 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Mon, 29 May 2023 14:51:44 +0200 Subject: [PATCH] intial commit --- .gitignore | 54 ++ source/mijin/SConscript | 24 + source/mijin/async/coroutine.cpp | 213 ++++++ source/mijin/async/coroutine.hpp | 648 ++++++++++++++++++ source/mijin/async/future.hpp | 140 ++++ source/mijin/async/message_queue.hpp | 132 ++++ source/mijin/async/signal.hpp | 126 ++++ source/mijin/container/boxed_object.hpp | 237 +++++++ source/mijin/container/cached_array.hpp | 145 ++++ source/mijin/container/inline_array.hpp | 133 ++++ source/mijin/container/map_view.hpp | 90 +++ source/mijin/container/optional.hpp | 298 ++++++++ source/mijin/container/typeless_buffer.hpp | 104 +++ source/mijin/debug/assert.hpp | 146 ++++ source/mijin/debug/symbol_info.cpp | 87 +++ source/mijin/debug/symbol_info.hpp | 33 + source/mijin/detect.hpp | 78 +++ source/mijin/io/stream.cpp | 308 +++++++++ source/mijin/io/stream.hpp | 181 +++++ source/mijin/memory/data_pool.cpp | 31 + source/mijin/memory/data_pool.hpp | 90 +++ source/mijin/types/name.cpp | 128 ++++ source/mijin/types/name.hpp | 63 ++ source/mijin/util/bitarray.hpp | 75 ++ source/mijin/util/bitflags.hpp | 92 +++ source/mijin/util/concepts.hpp | 48 ++ source/mijin/util/flag.hpp | 161 +++++ source/mijin/util/os.cpp | 47 ++ source/mijin/util/os.hpp | 30 + source/mijin/util/string.hpp | 52 ++ source/mijin/util/template_string.hpp | 126 ++++ source/mijin/util/traits.hpp | 118 ++++ source/mijin/util/types.hpp | 127 ++++ .../mijin/virtual_filesystem/filesystem.cpp | 128 ++++ .../mijin/virtual_filesystem/filesystem.hpp | 105 +++ source/mijin/virtual_filesystem/relative.hpp | 87 +++ source/mijin/virtual_filesystem/stacked.cpp | 105 +++ source/mijin/virtual_filesystem/stacked.hpp | 52 ++ 38 files changed, 4842 insertions(+) create mode 100644 .gitignore create mode 100644 source/mijin/SConscript create mode 100644 source/mijin/async/coroutine.cpp create mode 100644 source/mijin/async/coroutine.hpp create mode 100644 source/mijin/async/future.hpp create mode 100644 source/mijin/async/message_queue.hpp create mode 100644 source/mijin/async/signal.hpp create mode 100644 source/mijin/container/boxed_object.hpp create mode 100644 source/mijin/container/cached_array.hpp create mode 100644 source/mijin/container/inline_array.hpp create mode 100644 source/mijin/container/map_view.hpp create mode 100644 source/mijin/container/optional.hpp create mode 100644 source/mijin/container/typeless_buffer.hpp create mode 100644 source/mijin/debug/assert.hpp create mode 100644 source/mijin/debug/symbol_info.cpp create mode 100644 source/mijin/debug/symbol_info.hpp create mode 100644 source/mijin/detect.hpp create mode 100644 source/mijin/io/stream.cpp create mode 100644 source/mijin/io/stream.hpp create mode 100644 source/mijin/memory/data_pool.cpp create mode 100644 source/mijin/memory/data_pool.hpp create mode 100644 source/mijin/types/name.cpp create mode 100644 source/mijin/types/name.hpp create mode 100644 source/mijin/util/bitarray.hpp create mode 100644 source/mijin/util/bitflags.hpp create mode 100644 source/mijin/util/concepts.hpp create mode 100644 source/mijin/util/flag.hpp create mode 100644 source/mijin/util/os.cpp create mode 100644 source/mijin/util/os.hpp create mode 100644 source/mijin/util/string.hpp create mode 100644 source/mijin/util/template_string.hpp create mode 100644 source/mijin/util/traits.hpp create mode 100644 source/mijin/util/types.hpp create mode 100644 source/mijin/virtual_filesystem/filesystem.cpp create mode 100644 source/mijin/virtual_filesystem/filesystem.hpp create mode 100644 source/mijin/virtual_filesystem/relative.hpp create mode 100644 source/mijin/virtual_filesystem/stacked.cpp create mode 100644 source/mijin/virtual_filesystem/stacked.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3cb449 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Compile commands +compile_commands.json + +# whatever this is +.cache + +# Environment setup +/.env + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Debug Info +*.pdb + +# for projects that use SCons for building: http://http://www.scons.org/ +.sconsign.dblite + +# Byte-compiled / optimized python files +__pycache__/ +*.py[cod] + +# Backup files +*.bak diff --git a/source/mijin/SConscript b/source/mijin/SConscript new file mode 100644 index 0000000..2e28cdd --- /dev/null +++ b/source/mijin/SConscript @@ -0,0 +1,24 @@ + +Import('env') + +mijin_sources = Split(""" + async/coroutine.cpp + debug/symbol_info.cpp + io/stream.cpp + memory/data_pool.cpp + util/os.cpp + types/name.cpp + virtual_filesystem/filesystem.cpp + virtual_filesystem/stacked.cpp +""") + +env.UnityStaticLibrary( + target = env['LIB_DIR'] + '/mijin_sekiei', + source = mijin_sources +) + +LIB_CONFIG = { + 'LIBS': ['mijin_sekiei'] +} + +Return('LIB_CONFIG') diff --git a/source/mijin/async/coroutine.cpp b/source/mijin/async/coroutine.cpp new file mode 100644 index 0000000..71b25c4 --- /dev/null +++ b/source/mijin/async/coroutine.cpp @@ -0,0 +1,213 @@ + +#include "./coroutine.hpp" + +#include +#include +#include "../util/os.hpp" + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +thread_local TaskLoop::StoredTask* MultiThreadedTaskLoop::currentTask_ = nullptr; + +// +// 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 it = std::remove_if(parkedTasks_.begin(), parkedTasks_.end(), [](StoredTask& task) { + return !task.task || task.task->status() == TaskStatus::FINISHED; + }); + parkedTasks_.erase(it, 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 + currentTask_ = &*task; + tickTask(*task); + currentTask_ = nullptr; + + // and give it back + returningTasks_.push(std::move(*task)); + } +} + +// +// public functions +// + +void SimpleTaskLoop::transferCurrentTask(TaskLoop& otherLoop) 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) 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)); + } +} + +void MultiThreadedTaskLoop::transferCurrentTask(TaskLoop& otherLoop) noexcept +{ + if (&otherLoop == this) { + return; + } + + MIJIN_ASSERT_FATAL(currentTask_ != nullptr, "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 MultiThreadedTaskLoop::addStoredTask(StoredTask&& storedTask) 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 new file mode 100644 index 0000000..20f039b --- /dev/null +++ b/source/mijin/async/coroutine.hpp @@ -0,0 +1,648 @@ + +#pragma once + +#ifndef MIJIN_ASYNC_COROUTINE_HPP_INCLUDED +#define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include + +#include "./future.hpp" +#include "./message_queue.hpp" +#include "../container/optional.hpp" +#include "../util/flag.hpp" +#include "../util/traits.hpp" + +namespace mijin +{ + +// +// public defines +// + +#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO) +#define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 +#endif + +// +// public types +// + +enum class TaskStatus +{ + SUSPENDED = 0, + RUNNING = 1, + WAITING = 2, + FINISHED = 3, + YIELDED = 4 +}; + +// forward declarations +template +struct TaskState; + +class TaskLoop; + +template +class TaskBase; + +namespace impl +{ +template +struct TaskReturn +{ + template + constexpr void return_value(TArgs&&... args) noexcept { + *(static_cast(*this).state_) = TaskState(TReturn(std::forward(args)...), TaskStatus::FINISHED); + } + + constexpr void return_value(TReturn value) noexcept { + *(static_cast(*this).state_) = TaskState(TReturn(std::move(value)), TaskStatus::FINISHED); + } +}; + +template +struct TaskReturn +{ + constexpr void return_void() noexcept { + static_cast(*this).state_->status = TaskStatus::FINISHED; + } +}; +} // namespace impl + +template +struct TaskState +{ + Optional value; + TaskStatus status = TaskStatus::SUSPENDED; + + TaskState() = default; + TaskState(const TaskState&) = default; + TaskState(TaskState&&) noexcept = default; + constexpr TaskState(T _value, TaskStatus _status) noexcept : value(std::move(_value)), status(_status) {} + TaskState& operator=(const TaskState&) = default; + TaskState& operator=(TaskState&&) noexcept = default; +}; +template<> +struct TaskState +{ + TaskStatus status = TaskStatus::SUSPENDED; + + TaskState() = default; + TaskState(const TaskState&) = default; + TaskState(TaskState&&) noexcept = default; + constexpr TaskState(TaskStatus _status) noexcept : status(_status) {} + TaskState& operator=(const TaskState&) = default; + TaskState& operator=(TaskState&&) noexcept = default; +}; + +template +struct TaskAwaitableFuture +{ + FuturePtr future; + + [[nodiscard]] constexpr bool await_ready() const noexcept { return future->ready(); } + constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} + constexpr TValue await_resume() const noexcept { + if constexpr (std::is_same_v) { + return; + } + else { + return std::move(future->get()); + } + } +}; + +template +struct TaskAwaitableSignal +{ + std::shared_ptr> data; + + [[nodiscard]] constexpr bool await_ready() const noexcept { return false; } + constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} + constexpr auto& await_resume() const noexcept { + return *data; + } +}; + +template +struct TaskAwaitableSignal +{ + std::shared_ptr data; + + [[nodiscard]] constexpr bool await_ready() const noexcept { return false; } + constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} + constexpr auto& await_resume() const noexcept { + return *data; + } +}; + +template<> +struct TaskAwaitableSignal<> +{ + [[nodiscard]] constexpr bool await_ready() const noexcept { return false; } + constexpr void await_suspend(std::coroutine_handle<>) const noexcept {} + constexpr void await_resume() const noexcept {} +}; + +template +struct TaskPromise : impl::TaskReturn> +{ + using handle_t = std::coroutine_handle; + using task_t = typename TTraits::task_t; + using result_t = typename TTraits::result_t; + + std::shared_ptr> state_ = std::make_shared>(); + TaskLoop* loop_ = nullptr; + + constexpr task_t get_return_object() noexcept { return task_t(handle_t::from_promise(*this)); } + constexpr std::suspend_always initial_suspend() noexcept { return {}; } + constexpr std::suspend_always final_suspend() noexcept { return {}; } + + // template + // constexpr std::suspend_always yield_value(TValue value) noexcept { + // *state_ = TaskState(std::move(value), TaskStatus::YIELDED); + // return {}; + // } + + // TODO: implement yielding (can't use futures for this) + + constexpr void unhandled_exception() noexcept {} + + template + auto await_transform(FuturePtr future) noexcept + { + MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!"); + TaskAwaitableFuture awaitable{future}; + if (!awaitable.await_ready()) + { + state_->status = TaskStatus::WAITING; + future->sigSet.connect([this, future]() mutable + { + state_->status = TaskStatus::SUSPENDED; + }, Oneshot::YES); + } + return awaitable; + } + + template + auto await_transform(TaskBase task) noexcept + { + MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!"); + auto future = delayEvaluation(loop_)->addTask(std::move(task)); // hackidyhack: delay evaluation of the type of loop_ as it is only forward-declared here + return await_transform(future); + } + + template + auto await_transform(Signal& signal) noexcept + { + auto data = std::make_shared>(); + signal.connect([this, data](TFirstArg arg0, TSecondArg arg1, TArgs... args) mutable + { + *data = std::make_tuple(std::move(arg0), std::move(arg1), std::move(args)...); + state_->status = TaskStatus::SUSPENDED; + }, Oneshot::YES); + TaskAwaitableSignal awaitable{data}; + state_->status = TaskStatus::WAITING; + return awaitable; + } + + template + auto await_transform(Signal& signal) noexcept + { + auto data = std::make_shared(); + signal.connect([this, data](TFirstArg arg0) mutable + { + *data = std::move(arg0); + state_->status = TaskStatus::SUSPENDED; + }, Oneshot::YES); + TaskAwaitableSignal awaitable{data}; + state_->status = TaskStatus::WAITING; + return awaitable; + } + + auto await_transform(Signal<>& signal) noexcept + { + signal.connect([this]() + { + state_->status = TaskStatus::SUSPENDED; + }, Oneshot::YES); + TaskAwaitableSignal<> awaitable{}; + state_->status = TaskStatus::WAITING; + return awaitable; + } + + std::suspend_always await_transform(std::suspend_always) noexcept + { + state_->status = TaskStatus::SUSPENDED; + return std::suspend_always(); + } + + std::suspend_never await_transform(std::suspend_never) noexcept { + return std::suspend_never(); + } +}; + +template +class TaskBase +{ +public: + using task_t = TaskBase; + using result_t = TResult; + struct Traits + { + using task_t = TaskBase; + using result_t = TResult; + }; +public: + using promise_type = TaskPromise; + using handle_t = typename promise_type::handle_t; +private: + handle_t handle_; + std::shared_ptr> state_; +public: + constexpr explicit TaskBase(handle_t handle) noexcept : handle_(handle), state_(handle.promise().state_) {} + TaskBase(const TaskBase&) = default; + TaskBase(TaskBase&& other) noexcept = default; + ~TaskBase() noexcept; +public: + TaskBase& operator=(const TaskBase&) = default; + TaskBase& operator=(TaskBase&& other) noexcept = default; + + [[nodiscard]] + constexpr bool operator==(const TaskBase& other) const noexcept { return handle_ == other.handle_; } + + [[nodiscard]] + constexpr bool operator!=(const TaskBase& other) const noexcept { return handle_ != other.handle_; } +public: + constexpr TaskState& resume() noexcept + { + state_->status = TaskStatus::RUNNING; + handle_.resume(); + return *state_; + } + + [[nodiscard]] + constexpr TaskState& state() noexcept + { + return *state_; + } +private: + [[nodiscard]] + constexpr handle_t handle() const noexcept { return handle_; } + [[nodiscard]] + constexpr TaskLoop* getLoop() noexcept + { + return handle_.promise().loop_; + } + constexpr void setLoop(TaskLoop* loop) noexcept + { + // MIJIN_ASSERT(handle_.promise().loop_ == nullptr + // || handle_.promise().loop_ == loop + // || loop == nullptr, "Task already has a loop assigned!"); + handle_.promise().loop_ = loop; + } + + friend class TaskLoop; + + template + friend class WrappedTask; +}; + +class WrappedTaskBase +{ +public: + virtual ~WrappedTaskBase() = default; +public: + virtual TaskStatus status() noexcept = 0; + // virtual std::any result() noexcept = 0; + virtual void resume() noexcept = 0; + virtual void* raw() noexcept = 0; + virtual std::coroutine_handle<> handle() noexcept = 0; + virtual void setLoop(TaskLoop* loop) noexcept = 0; + + [[nodiscard]] inline bool canResume() { + const TaskStatus stat = status(); + return (stat == TaskStatus::SUSPENDED || stat == TaskStatus::YIELDED); + } +}; + +template +class WrappedTask : public WrappedTaskBase +{ +private: + TTask task_; +public: + constexpr explicit WrappedTask(TTask&& task) noexcept : task_(std::move(task)) {} + WrappedTask(const WrappedTask&) = delete; + WrappedTask(WrappedTask&&) noexcept = default; +public: + WrappedTask& operator=(const WrappedTask&) = delete; + WrappedTask& operator=(WrappedTask&&) noexcept = default; +public: + TaskStatus status() noexcept override { return task_.state().status; } + // std::any result() noexcept override + // { + // if constexpr (std::is_same_v) { + // return {}; + // } + // else { + // return std::any(task_.state().value); + // } + // } + void resume() noexcept override { task_.resume(); } + void* raw() noexcept override { return &task_; } + std::coroutine_handle<> handle() noexcept override { return task_.handle(); } + void setLoop(TaskLoop* loop) noexcept override { task_.setLoop(loop); } +}; + +template +std::unique_ptr> wrapTask(TTask&& task) noexcept +{ + return std::make_unique>(std::forward(task)); +} + +class TaskLoop +{ +public: + MIJIN_DEFINE_FLAG(CanContinue); + MIJIN_DEFINE_FLAG(IgnoreWaiting); +protected: + using wrapped_task_t = WrappedTaskBase; + using wrapped_task_base_ptr_t = std::unique_ptr; + + struct StoredTask + { + wrapped_task_base_ptr_t task; + std::function setFuture; + std::any resultData; + }; + using task_vector_t = std::vector; + + template + using wrapped_task_ptr_t = std::unique_ptr>; +public: + TaskLoop() = default; + TaskLoop(const TaskLoop&) = delete; + TaskLoop(TaskLoop&&) = delete; + virtual ~TaskLoop() = default; + + TaskLoop& operator=(const TaskLoop&) = delete; + TaskLoop& operator=(TaskLoop&&) = delete; + + template + inline FuturePtr addTask(TaskBase task) noexcept; + + virtual void transferCurrentTask(TaskLoop& otherLoop) noexcept = 0; + virtual void addStoredTask(StoredTask&& storedTask) noexcept = 0; + + [[nodiscard]] static TaskLoop& current() noexcept; +protected: + inline TaskStatus tickTask(StoredTask& task) noexcept; +protected: + static inline TaskLoop*& currentLoopStorage() noexcept; + template + static inline void setFutureHelper(StoredTask& storedTask) noexcept; +}; + +template +using Task = TaskBase; + +class SimpleTaskLoop : public TaskLoop +{ +private: + task_vector_t tasks_; + task_vector_t newTasks_; + task_vector_t::iterator currentTask_; + MessageQueue queuedTasks_; + std::thread::id threadId_; + +public: // TaskLoop implementation + void transferCurrentTask(TaskLoop& otherLoop) noexcept override; + void addStoredTask(StoredTask&& storedTask) noexcept override; + +public: // public interface + [[nodiscard]] constexpr bool empty() const noexcept { return tasks_.empty() && newTasks_.empty(); } + inline CanContinue tick() noexcept; + inline void runUntilDone(IgnoreWaiting ignoreWaiting = IgnoreWaiting::NO) noexcept; +private: + inline void assertCorrectThread() { MIJIN_ASSERT(threadId_ == std::thread::id() || threadId_ == std::this_thread::get_id(), "Unsafe to TaskLoop from different thread!"); } +}; + +class MultiThreadedTaskLoop : public TaskLoop +{ +private: + 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_; + +public: // TaskLoop implementation + void transferCurrentTask(TaskLoop& otherLoop) noexcept override; + void addStoredTask(StoredTask&& storedTask) noexcept override; + +public: // public interface + void start(std::size_t numWorkerThreads); + void stop(); +private: // private stuff + void managerThread(std::stop_token stopToken); + void workerThread(std::stop_token stopToken, std::size_t workerId); + + static thread_local StoredTask* currentTask_; +}; + +// +// public functions +// + +template +TaskBase::~TaskBase() noexcept +{ + if (handle_) + { + // handle_.destroy(); + } +} + +template +inline FuturePtr TaskLoop::addTask(TaskBase task) noexcept +{ + MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!"); + task.setLoop(this); + + auto future = std::make_shared>(); + + auto setFuture = &setFutureHelper; + + // 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 + }); + + return future; +} + +inline TaskStatus TaskLoop::tickTask(StoredTask& task) noexcept +{ + TaskStatus status = {}; + 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); + + if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED) + { + task.setFuture(task); + } + return status; +} + +/* static */ inline auto TaskLoop::current() noexcept -> TaskLoop& +{ + MIJIN_ASSERT(currentLoopStorage() != nullptr, "Attempting to fetch current loop while no coroutine is running!"); + return *currentLoopStorage(); +} + +/* static */ auto TaskLoop::currentLoopStorage() noexcept -> TaskLoop*& +{ + static thread_local TaskLoop* storage = nullptr; + return storage; +} + +template +/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) noexcept +{ + TaskBase& task = *static_cast*>(storedTask.task->raw()); + auto future = std::any_cast>(storedTask.resultData); + + if constexpr (!std::is_same_v) + { + MIJIN_ASSERT(!task.state().value.empty(), "Task did not produce a value?"); + future->set(std::move(task.state().value.get())); + } + else { + future->set(); + } +} + +inline std::suspend_always switchContext(TaskLoop& taskLoop) +{ + TaskLoop& currentTaskLoop = TaskLoop::current(); + if (¤tTaskLoop == &taskLoop) { + return {}; + } + currentTaskLoop.transferCurrentTask(taskLoop); + return {}; +} + +inline auto SimpleTaskLoop::tick() noexcept -> CanContinue +{ + // set current taskloop + MIJIN_ASSERT(currentLoopStorage() == nullptr, "Trying to tick a loop from a coroutine, this is not supported."); + currentLoopStorage() = this; + threadId_ = std::this_thread::get_id(); + + // move over all tasks from newTasks + for (StoredTask& task : newTasks_) + { + tasks_.push_back(std::move(task)); + } + newTasks_.clear(); + + // also pick up tasks from other threads + while(true) + { + std::optional task = queuedTasks_.tryPop(); + if (!task.has_value()) { + break; + } + tasks_.push_back(std::move(*task)); + } + + // remove any tasks that are finished executing + auto it = std::remove_if(tasks_.begin(), tasks_.end(), [](StoredTask& task) { + return task.task->status() == TaskStatus::FINISHED; + }); + tasks_.erase(it, tasks_.end()); + + CanContinue canContinue = CanContinue::NO; + // then execute all tasks that can be executed + for (currentTask_ = tasks_.begin(); currentTask_ != tasks_.end(); ++currentTask_) + { + StoredTask& task = *currentTask_; + TaskStatus status = task.task->status(); + if (status != TaskStatus::SUSPENDED && status != TaskStatus::YIELDED) + { + MIJIN_ASSERT(status == TaskStatus::WAITING, "Task with invalid status in task list!"); + continue; + } + + status = tickTask(task); + + if (status == TaskStatus::SUSPENDED || status == TaskStatus::YIELDED) + { + canContinue = CanContinue::YES; + } + } + // reset current loop + currentLoopStorage() = nullptr; + + // remove any tasks that have been transferred to another queue + it = std::remove_if(tasks_.begin(), tasks_.end(), [](const StoredTask& task) { + return task.task == nullptr; + }); + tasks_.erase(it, tasks_.end()); + + return canContinue; +} + +inline void SimpleTaskLoop::runUntilDone(IgnoreWaiting ignoreWaiting) noexcept +{ + while (!tasks_.empty() || !newTasks_.empty()) + { + const CanContinue canContinue = tick(); + if (ignoreWaiting && !canContinue) + { + break; + } + } +} + +// utility stuff + +inline std::suspend_always c_suspend() { + return std::suspend_always(); +} + +template typename TCollection, typename TType, typename... TTemplateArgs> +Task<> c_allDone(const TCollection, TTemplateArgs...>& futures) +{ + bool allDone = true; + do + { + allDone = true; + for (const FuturePtr& future : futures) + { + if (future && !future->ready()) { + allDone = false; + break; + } + } + co_await c_suspend(); + } while (!allDone); +} + +#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO +#endif +} + +#endif // MIJIN_ASYNC_COROUTINE_HPP_INCLUDED diff --git a/source/mijin/async/future.hpp b/source/mijin/async/future.hpp new file mode 100644 index 0000000..0ccbc19 --- /dev/null +++ b/source/mijin/async/future.hpp @@ -0,0 +1,140 @@ + +#pragma once + +#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) +#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1 + +#include +#include +#include +#include "./signal.hpp" +#include "../debug/assert.hpp" +#include "../container/optional.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// +template +class Future; + +// TODO: add support for mutexes and waiting for futures +namespace impl +{ +template +struct FutureStorage +{ + std::optional value; + + void setValue(TValue value_) noexcept { value = std::move(value_); } + [[nodiscard]] TValue& getValue() noexcept { return value.value(); } + +}; + +template +struct FutureStorage +{ + std::optional value; + + void setValue(TValue& value_) noexcept { value = &value_; } + [[nodiscard]] TValue& getValue() const noexcept { return *value.value(); } +}; + +template<> +struct FutureStorage +{ +}; +} // namespace impl + +template +class Future +{ +private: + impl::FutureStorage value_; + bool isSet_ = false; +public: + Future() = default; + Future(const Future&) = delete; + Future(Future&&) noexcept = default; +public: + Future& operator=(const Future&) = delete; + Future& operator=(Future&&) noexcept = default; + + [[nodiscard]] + constexpr explicit operator bool() const noexcept { return ready(); } + + [[nodiscard]] + constexpr bool operator!() const noexcept { return !ready(); } +public: // access + [[nodiscard]] + constexpr decltype(auto) get() noexcept + { + MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready."); + if constexpr(std::is_same_v) { + return; + } + else { + return value_.getValue(); + } + } + [[nodiscard]] + constexpr decltype(auto) get() const noexcept + { + MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready."); + if constexpr(std::is_same_v) { + return; + } + else { + return value_.getValue(); + } + } + [[nodiscard]] + constexpr bool ready() const noexcept + { + return isSet_; + } +public: // modification + template requires (!std::is_same_v) + constexpr void set(TArg&& value) noexcept + { + MIJIN_ASSERT(!isSet_, "Trying to set a future twice!"); + value_.setValue(std::move(value)); + isSet_ = true; + sigSet.emit(); + } + constexpr void set() noexcept + { + MIJIN_ASSERT(!isSet_, "Trying to set a future twice!"); + isSet_ = true; + if constexpr (std::is_same_v) { + sigSet.emit(); + } + else { + // would love to make this a compile-time error :/ + MIJIN_ERROR("Attempting to call set(void) on future with value."); + } + } +public: // signals + Signal<> sigSet; +}; + +template +using FuturePtr = std::shared_ptr>; + +// +// public functions +// + +} // 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 new file mode 100644 index 0000000..a48791d --- /dev/null +++ b/source/mijin/async/message_queue.hpp @@ -0,0 +1,132 @@ + +#pragma once + +#if !defined(MIJIN_MESSAGE_QUEUE_HPP_INCLUDED) +#define MIJIN_MESSAGE_QUEUE_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include "../util/bitarray.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +class MessageQueue +{ +private: + 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&&) noexcept = delete; + + MessageQueue& operator=(const MessageQueue&) = delete; + MessageQueue& operator=(MessageQueue&&) noexcept = delete; + + [[nodiscard]] bool tryPushMaybeMove(TMessage& message); + [[nodiscard]] bool tryPush(TMessage message) { + return tryPushMaybeMove(message); + } + void push(TMessage message); + [[nodiscard]] std::optional tryPop(); + [[nodiscard]] TMessage wait(); +}; + +template +struct TaskMessageQueue +{ + MessageQueue requests; + MessageQueue responses; +}; + +// +// public functions +// + +template +bool MessageQueue::tryPushMaybeMove(TMessage& message) +{ + unsigned oldWritePos = writePos.load(std::memory_order_relaxed); + unsigned newWritePos = 0; + do + { + newWritePos = (oldWritePos + 1) % bufferSize; + if (newWritePos == readPos) { + return false; + } + } while (!writePos.compare_exchange_weak(oldWritePos, newWritePos, std::memory_order_release, std::memory_order_relaxed)); + + while (messageReady.get(oldWritePos)) { + std::this_thread::yield(); // someone is still reading, wait... + } + + messages[oldWritePos] = std::move(message); + messageReady.set(oldWritePos, true); + + return true; +} + +template +void MessageQueue::push(TMessage message) +{ + while (!tryPushMaybeMove(message)) { + std::this_thread::yield(); + } +} + +template +std::optional MessageQueue::tryPop() +{ + unsigned oldReadPos = readPos.load(std::memory_order_relaxed); + unsigned newReadPos = 0; + do + { + 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 (!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); + return message; +} + +template +TMessage MessageQueue::wait() +{ + while (true) + { + std::optional message = tryPop(); + if (message.has_value()) { + return message.value(); + } + std::this_thread::yield(); + } +} + +} // namespace mijin + +#endif // !defined(MIJIN_MESSAGE_QUEUE_HPP_INCLUDED) diff --git a/source/mijin/async/signal.hpp b/source/mijin/async/signal.hpp new file mode 100644 index 0000000..3602609 --- /dev/null +++ b/source/mijin/async/signal.hpp @@ -0,0 +1,126 @@ + +#pragma once + +#if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED) +#define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#include "../util/flag.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +MIJIN_DEFINE_FLAG(Oneshot); + +template +class Signal +{ +public: + using handler_t = std::function; + using token_t = std::uint32_t; +private: + struct RegisteredHandler + { + handler_t callable; + std::weak_ptr referenced; + token_t token; + Oneshot oneshot = Oneshot::NO; + }; + 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&&) noexcept = default; +public: + Signal& operator=(const Signal&) = delete; + Signal& operator=(Signal&&) noexcept = default; +public: + template + inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr referenced = std::weak_ptr()) noexcept; + inline void disconnect(token_t token) noexcept; + inline void emit(TArgs&&... args) noexcept; +}; + +// +// public functions +// + +template +template +inline auto Signal::connect(THandler handler, Oneshot oneshot, std::weak_ptr referenced) noexcept -> token_t +{ + std::lock_guard lock(handlersMutex_); + + auto callable = handler_t(handler); + handlers_.push_back({ + .callable = std::move(callable), + .referenced = std::move(referenced), + .token = nextToken, + .oneshot = oneshot + }); + + return nextToken++; +} + +template +inline void Signal::disconnect(token_t token) noexcept +{ + std::lock_guard lock(handlersMutex_); + + auto it = std::remove_if(handlers_.begin(), handlers_.end(), [token](const RegisteredHandler& handler) + { + return handler.token == token; + }); + handlers_.erase(it, handlers_.end()); +} + +template +inline void Signal::emit(TArgs&&... args) noexcept +{ + std::lock_guard lock(handlersMutex_); + + // first erase any handlers with expired references + // auto it = std::remove_if(handlers_.begin(), handlers_.end(), [](const RegisteredHandler& handler) + // { + // return handler.referenced.expired(); + // }); + // handlers_.erase(it, handlers_.end()); + // TODO: this doesn't really work since expired() also returns true if the pointer was never set + + // invoke all handlers + for (RegisteredHandler& handler : handlers_) + { + handler.callable(forward(args)...); + } + + // remove any oneshot + auto it = std::remove_if(handlers_.begin(), handlers_.end(), [](const RegisteredHandler& handler) + { + return handler.oneshot; + }); + handlers_.erase(it, handlers_.end()); +} + +} // namespace mijin + +#endif // !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED) diff --git a/source/mijin/container/boxed_object.hpp b/source/mijin/container/boxed_object.hpp new file mode 100644 index 0000000..a0a44b3 --- /dev/null +++ b/source/mijin/container/boxed_object.hpp @@ -0,0 +1,237 @@ + +#pragma once + +#if !defined(MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED) +#define MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED 1 + +#include +#include +#include "../debug/assert.hpp" + +namespace mijin +{ + +// +// public defines +// + +#if !defined(MIJIN_BOXED_OBJECT_DEBUG) +#define MIJIN_BOXED_OBJECT_DEBUG MIJIN_DEBUG +#endif + +// +// public constants +// + +// +// public types +// + +template +class BoxedObject +{ +private: + union { + T object_; + }; +#if MIJIN_BOXED_OBJECT_DEBUG + bool constructed = false; +#endif +public: + BoxedObject() = default; + explicit BoxedObject(T object) +#if MIJIN_BOXED_OBJECT_DEBUG + : constructed(true) +#endif +{ + std::construct_at(&object_, std::move(object)); + } + BoxedObject(const BoxedObject&) = delete; + BoxedObject(BoxedObject&&) = delete; + +#if MIJIN_BOXED_OBJECT_DEBUG + ~BoxedObject() + { + MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroy prior to destructor!") + } +#endif + + BoxedObject& operator=(const BoxedObject&) = delete; + BoxedObject& operator=(BoxedObject&&) = delete; + + template + void construct(TArgs&&... args) + { +#if MIJIN_BOXED_OBJECT_DEBUG + MIJIN_ASSERT(!constructed, "BoxedObject::construct(): Attempt to construct an already constructed object!") + constructed = true; +#endif + std::construct_at(&object_, std::forward(args)...); + } + + void destroy() + { +#if MIJIN_BOXED_OBJECT_DEBUG + MIJIN_ASSERT(constructed, "BoxedObject::destroy(): Attempt to destroy a not constructed object!") + constructed = false; +#endif + std::destroy_at(&object_); + } + + void copyTo(BoxedObject& other) const + { +#if MIJIN_BOXED_OBJECT_DEBUG + MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!") +#endif + other.construct(object_); + } + + void moveTo(BoxedObject& other) + { +#if MIJIN_BOXED_OBJECT_DEBUG + MIJIN_ASSERT(constructed, "BoxedObject::copy(): Attempt to copy a not constructed object!") +#endif + other.construct(std::move(object_)); + destroy(); + } + + [[nodiscard]] T& get() + { +#if MIJIN_BOXED_OBJECT_DEBUG + MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!") +#endif + return object_; + } + + [[nodiscard]] const T& get() const + { +#if MIJIN_BOXED_OBJECT_DEBUG + MIJIN_ASSERT(constructed, "BoxedObject::get(): Attempt to access a not constructed object!") +#endif + return object_; + } +}; + +template +class BoxedObjectIterator +{ +public: + using difference_type = std::ptrdiff_t; + using value_type = T; +private: + BoxedObject* box_ = nullptr; +#if MIJIN_CHECKED_ITERATORS + BoxedObject* start_ = nullptr; + BoxedObject* end_ = nullptr; +#endif +public: + BoxedObjectIterator() = default; + explicit constexpr BoxedObjectIterator(BoxedObject* box, BoxedObject* start, BoxedObject* end) + : box_(box) +#if MIJIN_CHECKED_ITERATORS + , start_(start), end_(end) +#endif + {} + BoxedObjectIterator(const BoxedObjectIterator&) = default; + 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 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]] 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]] T& operator[](difference_type diff) const { return *(*this + diff); } +}; +template +inline BoxedObjectIterator operator+(std::iter_difference_t diff, const BoxedObjectIterator& iter) { + return iter + diff; +} +static_assert(std::random_access_iterator>); + +// +// public functions +// + +template +BoxedObjectIterator& BoxedObjectIterator::operator+=(difference_type diff) +{ +#if MIJIN_CHECKED_ITERATORS + MIJIN_ASSERT(diff <= (end_ - box_) && diff >= (box_ - start_), "BoxedObjectIterator::operator+=(): Attempt to iterate out of range."); +#endif + box_ += diff; + return *this; +} + +template +T& BoxedObjectIterator::operator*() const +{ +#if MIJIN_CHECKED_ITERATORS + MIJIN_ASSERT(box_ >= start_ && box < end_, "BoxedObjectIterator::operator->(): Attempt to dereference out-of-range iterator."); +#endif + return box_->get(); +} + +template +BoxedObjectIterator& BoxedObjectIterator::operator++() +{ +#if MIJIN_CHECKED_ITERATORS + MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end."); +#endif + ++box_; +} + +template +BoxedObjectIterator BoxedObjectIterator::operator++(int) +{ +#if MIJIN_CHECKED_ITERATORS + MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(int): Attempt to iterator past the end."); +#endif + BoxedObjectIterator copy(*this); + ++box_; + return copy; +} + +template +BoxedObjectIterator& BoxedObjectIterator::operator--() +{ +#if MIJIN_CHECKED_ITERATORS + MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start."); +#endif + --box_; +} + +template +BoxedObjectIterator BoxedObjectIterator::operator--(int) +{ +#if MIJIN_CHECKED_ITERATORS + MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(int): Attempt to iterator past start."); +#endif + BoxedObjectIterator copy(*this); + --box_; + return copy; +} + +template +BoxedObjectIterator BoxedObjectIterator::operator+(difference_type diff) const +{ + BoxedObjectIterator copy(*this); + copy += diff; + return copy; +} + +} // namespace mijin + +#endif // !defined(MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED) diff --git a/source/mijin/container/cached_array.hpp b/source/mijin/container/cached_array.hpp new file mode 100644 index 0000000..ac5af19 --- /dev/null +++ b/source/mijin/container/cached_array.hpp @@ -0,0 +1,145 @@ + +#pragma once + +#if !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED) +#define MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#include +#include "../debug/assert.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +class CachedArray +{ +public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = T*; + using const_iterator = const T*; + + static constexpr std::size_t INVALID_INDEX = std::numeric_limits::max(); +private: + struct CacheEntry + { + size_type index = INVALID_INDEX; + std::size_t useIndex = 0; + std::optional value; + }; + mutable std::array cache_; + std::vector> backbuffer_; + std::size_t nextUseIndex_ = 1; +public: + [[nodiscard]] reference at(size_type index); + [[nodiscard]] const_reference at(size_type index) const; + [[nodiscard]] size_type size() const { return backbuffer_.size(); } + void resize(size_type newSize); + void reserve(size_type elements); + + // TODO: make iterable (not that easy if we move from the backbuffer!) + // [[nodiscard]] iterator begin() { return &backbuffer_[0]; } + // [[nodiscard]] const_iterator begin() const { return &backbuffer_[0]; } + // [[nodiscard]] const_iterator cbegin() const { return &backbuffer_[0]; } + // [[nodiscard]] iterator end() { return begin() + size(); } + // [[nodiscard]] const_iterator end() const { return begin() + size(); } + // [[nodiscard]] const_iterator cend() const { return begin() + size(); } + + [[nodiscard]] reference operator[](size_type index) { return at(index); } + [[nodiscard]] const_reference operator[](size_type index) const { return at(index); } +private: + // TODO: use deducing this once it is available + template + static auto atImpl(TSelf&& self, std::size_t index); +}; + +// +// public functions +// + +template +auto CachedArray::at(std::size_t index) -> reference +{ + return atImpl(*this, index); +} + +template +auto CachedArray::at(std::size_t index) const -> const_reference +{ + return atImpl(*this, index); +} + +template +void CachedArray::resize(size_type newSize) +{ + if (newSize < size()) + { + // invalidate any cache entries that shouldn't exist anymore + for (CacheEntry& entry : cache_) + { + if (entry.index < newSize) + { + entry.value = std::nullopt; + entry.useIndex = 0; + } + } + } + backbuffer_.resize(newSize); +} + +template +template +auto CachedArray::atImpl(TSelf&& self, std::size_t index) +{ + MIJIN_ASSERT(index < size(), "CachedArray::at(): Attempting to access index out of range."); + // first try the cache + for (CacheEntry& entry : self.cache_) + { + if (entry.index == index) + { + entry.useIndex = self.nextUseIndex_++; + return *entry.value; + } + } + + // otherwise copy from backbuffer to cache + auto itCache = std::min_element(self.cache_.begin(), self.cache_.end(), [](const CacheEntry& left, const CacheEntry& right) { + return left.useIndex < right.useIndex; + }); + if (itCache->index != INVALID_INDEX) + { + // move back to the backbuffer + self.backbuffer_[itCache->index] = std::move(itCache->value); + } + itCache->index = index; + itCache->value = std::move(self.backbuffer_.at(index)); + itCache->useIndex = self.nextUseIndex_++; + return *itCache->value; +} + +} // namespace mijin + +#endif // !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED) diff --git a/source/mijin/container/inline_array.hpp b/source/mijin/container/inline_array.hpp new file mode 100644 index 0000000..e832667 --- /dev/null +++ b/source/mijin/container/inline_array.hpp @@ -0,0 +1,133 @@ + +#pragma once + +#if !defined(MIJIN_CONTAINER_INLINE_ARRAY_HPP_INCLUDED) +#define MIJIN_CONTAINER_INLINE_ARRAY_HPP_INCLUDED 1 + +#include "./boxed_object.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +class InlineArray +{ +public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = BoxedObjectIterator; + using const_iterator = BoxedObjectIterator; +private: + BoxedObject elements_[maxSize]; // NOLINT(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) + size_type size_ = 0; +public: + InlineArray() = default; + InlineArray(const InlineArray& other); + InlineArray(InlineArray&& other) noexcept; + ~InlineArray(); + + reference operator[](size_type index); + const_reference operator[](size_type index) const; + + [[nodiscard]] iterator begin() { return BoxedObjectIterator(&elements_[0], &elements_[0], &elements_[size_]); } + [[nodiscard]] const_iterator begin() const { return BoxedObjectIterator(&elements_[0], &elements_[0], &elements_[size_]); } + [[nodiscard]] const_iterator cbegin() const { return BoxedObjectIterator(&elements_[0], &elements_[0], &elements_[size_]); } + [[nodiscard]] iterator end() { return BoxedObjectIterator(&elements_[size_], &elements_[0], &elements_[size_]); } + [[nodiscard]] const_iterator end() const { return BoxedObjectIterator(&elements_[size_], &elements_[0], &elements_[size_]); } + [[nodiscard]] const_iterator cend() const { return BoxedObjectIterator(&elements_[size_], &elements_[0], &elements_[size_]); } + + [[nodiscard]] constexpr size_type size() const { return size_; } + [[nodiscard]] constexpr size_type capacity() const { return maxSize; } + [[nodiscard]] constexpr bool empty() const { return size_ == 0; } + [[nodiscard]] constexpr reference at(size_type index) { + MIJIN_ASSERT(index < size_, "InlineArray::at() attempt to access out-of-bounds index."); + return elements_[index].get(); + } + [[nodiscard]] constexpr const_reference at(size_type index) const { + MIJIN_ASSERT(index < size_, "InlineArray::at() attempt to access out-of-bounds index."); + return elements_[index].get(); + } + + void resize(size_type newSize); + void clear() { resize(0); } +}; + +// +// public functions +// + +template +InlineArray::InlineArray(const InlineArray& other) : size_(other.size_) +{ + for (size_type idx = 0; idx < size_; ++idx) { + other.elements_[idx].copyTo(elements_[idx]); + } +} + +template +InlineArray::InlineArray(InlineArray&& other) noexcept : size_(other.size_) +{ + for (size_type idx = 0; idx < size_; ++idx) { + other.elements_[idx].moveTo(elements_[idx]); + } + other.size_ = 0; +} + +template +InlineArray::~InlineArray() +{ + for (std::size_t idx = 0; idx < size_; ++idx) { + elements_[idx].destroy(); + } +} + +template +auto InlineArray::operator[](size_type index) -> reference +{ + return at(index); +} + +template +auto InlineArray::operator[](size_type index) const -> const_reference +{ + return at(index); +} + +template +void InlineArray::resize(size_type newSize) +{ + MIJIN_ASSERT(newSize <= numElements, "InlineArray::resize(): Attempt to resize beyond maximum size."); + if (newSize < size_) { + for (size_type idx = newSize; idx < size_; ++idx) { + elements_[idx].destroy(); + } + } + else { + for (size_type idx = size_; idx < newSize; ++idx) { + elements_[idx].construct(); + } + } + size_ = newSize; +} + +} // namespace mijin + +#endif // !defined(MIJIN_CONTAINER_INLINE_ARRAY_HPP_INCLUDED) diff --git a/source/mijin/container/map_view.hpp b/source/mijin/container/map_view.hpp new file mode 100644 index 0000000..a70afc0 --- /dev/null +++ b/source/mijin/container/map_view.hpp @@ -0,0 +1,90 @@ + +#pragma once + +#if !defined(MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED) +#define MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED 1 + +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template> +struct MapView +{ +private: + TMap& map; +public: + MapView() = delete; + MapView(const MapView&) = default; + MapView(MapView&&) noexcept = default; + inline MapView(TMap& map_) : map(map_) {} + + MapView& operator=(const MapView&) = default; + MapView& operator=(MapView&&) noexcept = default; + + [[nodiscard]] TValue& operator[](const TKey& key) + { + return at(key); + } + [[nodiscard]] const TValue& operator[](const TKey& key) const + { + return at(key); + } + + [[nodiscard]] TValue& at(const TKey& key) + { + return map.at(key); + } + + [[nodiscard]] const TValue& at(const TKey& key) const + { + return map.at(key); + } + + [[nodiscard]] auto begin() const + { + return map.begin(); + } + + [[nodiscard]] auto begin() + { + return map.begin(); + } + + [[nodiscard]] auto end() + { + return map.end(); + } + + [[nodiscard]] auto end() const + { + return map.end(); + } +}; + +// template typename TMap> +// MapView(TMap& map) -> MapView>; + +template +MapView(TMap& map) -> MapView; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED) diff --git a/source/mijin/container/optional.hpp b/source/mijin/container/optional.hpp new file mode 100644 index 0000000..46304c3 --- /dev/null +++ b/source/mijin/container/optional.hpp @@ -0,0 +1,298 @@ + +#pragma once + +#if !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED) +#define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1 + +#include +#include +#include "../debug/assert.hpp" +#include "../util/concepts.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +namespace impl +{ +template +struct OptionalStorage +{ + alignas(T) std::uint8_t data[sizeof(T)]; // NOLINT(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) + std::uint8_t used = 0; + + [[nodiscard]] + constexpr bool empty() const noexcept { return !used; } + + template + constexpr void emplace(TArgs&&... args) noexcept + { + MIJIN_ASSERT(!used, "Attempting to emplace in an already set OptionalStorage!"); + used = 1; + ::new (data) T(std::forward(args)...); + } + + void clear() + { + MIJIN_ASSERT(used, "Attempting to clear an empty OptionalStorage!"); + get().~T(); + used = 0; + } + + [[nodiscard]] constexpr T& get() noexcept { return *reinterpret_cast(data); } + [[nodiscard]] constexpr const T& get() const noexcept { return *reinterpret_cast(data); } +}; + +template +struct OptionalStorage +{ + static constexpr T invalidPointer() noexcept { return reinterpret_cast(1); } + + T ptr = invalidPointer(); + + [[nodiscard]] + constexpr bool empty() const noexcept { return ptr == invalidPointer(); } + + void emplace(T value) noexcept + { + ptr = value; + } + + void clear() + { + ptr = invalidPointer(); + } + + [[nodiscard]] T& get() noexcept { return ptr; } + [[nodiscard]] const T& get() const noexcept { return ptr; } +}; + +template +struct OptionalStorage +{ + using pointer_t = std::remove_reference_t*; + + pointer_t ptr = nullptr; + + [[nodiscard]] + constexpr bool empty() const noexcept { return ptr == nullptr; } + + void emplace(T value) noexcept + { + ptr = &value; + } + + void clear() + { + ptr = nullptr; + } + + T get() noexcept { return *ptr; } + const T get() const noexcept { return *ptr; } // NOLINT(readability-const-return-type) T already is a reference +}; +} +struct NullOptional {}; +static constexpr NullOptional NULL_OPTIONAL; + +template +class Optional +{ +private: + impl::OptionalStorage storage_; +public: + constexpr Optional() = default; + constexpr Optional(NullOptional) noexcept {} + inline Optional(const Optional& other) noexcept; + inline Optional(Optional&& other) noexcept; + inline Optional(TValue value) noexcept; + inline ~Optional() noexcept; +public: + Optional& operator =(const Optional& other) noexcept; + Optional& operator =(Optional&& other) noexcept; + Optional& operator =(TValue value) noexcept; + Optional& operator =(NullOptional) noexcept; +public: + [[nodiscard]] + constexpr bool operator ==(NullOptional) const noexcept { return empty(); } + template + [[nodiscard]] + constexpr bool operator ==(const Optional& other) const noexcept; + template + [[nodiscard]] + constexpr bool operator ==(const T& value) const noexcept; + template + [[nodiscard]] + constexpr bool operator !=(const T& value) const noexcept { return !(*this == value); } + [[nodiscard]] + constexpr explicit operator bool() const noexcept { return !empty(); } + [[nodiscard]] + constexpr bool operator !() const noexcept { return empty(); } +public: + template + void emplace(Types&&... params) noexcept; +public: + [[nodiscard]] inline std::remove_reference_t& get() noexcept; + [[nodiscard]] inline const std::remove_reference_t& get() const noexcept; + [[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); } + inline void reset() noexcept; +}; + +// +// public functions +// + +template +Optional::Optional(const Optional& other) noexcept +{ + if (other) { + emplace(other.get()); + } +} + +template +Optional::Optional(Optional&& other) noexcept +{ + if (other) + { + emplace(std::move(other.get())); + other.reset(); + } +} + +template +Optional::Optional(TValue value) noexcept +{ + if constexpr (std::is_reference_v) { + emplace(value); + } + else { + emplace(std::move(value)); + } +} + +template +Optional::~Optional() noexcept +{ + reset(); +} + +template +auto Optional::operator =(const Optional& other) noexcept -> Optional& +{ + if (&other == this) { + return *this; + } + reset(); + if (other) { + emplace(other.get()); + } + return *this; +} + +template +auto Optional::operator =(Optional&& other) noexcept -> Optional& +{ + if (&other == this) { + return *this; + } + reset(); + if (other) + { + emplace(std::move(other.get())); + other.reset(); + } + return *this; +} + +template +auto Optional::operator =(TValue value) noexcept -> Optional& +{ + if constexpr (std::is_reference_v) { + emplace(value); + } + else { + emplace(std::move(value)); + } + return *this; +} + +template +auto Optional::operator =(NullOptional) noexcept -> Optional& +{ + reset(); + return *this; +} + +template +template +constexpr bool Optional::operator ==(const Optional& other) const noexcept +{ + if (empty()) + { + return other.empty(); + } + if (!other.empty()) + { + return get() == other.get(); + } + return false; +} + +template +template +constexpr bool Optional::operator ==(const T& value) const noexcept +{ + if (empty()) + { + return false; + } + return get() == value; +} + +template +template +void Optional::emplace(Types&&... params) noexcept +{ + reset(); + storage_.emplace(std::forward(params)...); + MIJIN_ASSERT(!empty(), "Something is wrong."); +} + +template +inline std::remove_reference_t& Optional::get() noexcept +{ + MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!"); + return storage_.get(); +} + +template +inline const std::remove_reference_t& Optional::get() const noexcept +{ + MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!"); + return storage_.get(); +} + +template +inline void Optional::reset() noexcept +{ + if (empty()) { + return; + } + + storage_.clear(); +} + +} // namespace mijin + +#endif // !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED) diff --git a/source/mijin/container/typeless_buffer.hpp b/source/mijin/container/typeless_buffer.hpp new file mode 100644 index 0000000..a9c8a20 --- /dev/null +++ b/source/mijin/container/typeless_buffer.hpp @@ -0,0 +1,104 @@ + +#pragma once + +#if !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED) +#define MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED 1 + +#include +#include +#include "../debug/assert.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +class BufferView; + +class TypelessBuffer +{ +public: + using size_type = std::size_t; +private: + std::vector bytes_; +public: + [[nodiscard]] void* data() { return bytes_.data(); } + [[nodiscard]] const void* data() const { return bytes_.data(); } + [[nodiscard]] size_type byteSize() const { return bytes_.size(); } + void resize(size_type numBytes) { bytes_.resize(numBytes); } + void reserve(size_type numBytes) { bytes_.reserve(numBytes); } + + template + [[nodiscard]] BufferView makeBufferView() { return BufferView(this); } +}; + +template +class BufferView +{ +public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = T*; + using const_iterator = const T*; +private: + class TypelessBuffer* buffer_ = nullptr; +public: + BufferView() = default; + explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {} + BufferView(const BufferView&) = default; + + BufferView& operator=(const BufferView&) = default; + + [[nodiscard]] inline size_type size() const { return buffer_ ? buffer_->byteSize() / sizeof(T) : 0; } + inline void resize(size_type numElements) { + MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer."); + buffer_->resize(numElements * sizeof(T)); + } + inline void reserve(size_type numElements) { + MIJIN_ASSERT(buffer_, "BufferView::resize(): cannot resize a view without a buffer."); + buffer_->reserve(numElements * sizeof(T)); + } + + [[nodiscard]] inline iterator begin() { return buffer_ ? static_cast(buffer_->data()) : nullptr; } + [[nodiscard]] inline const_iterator begin() const { return buffer_ ? static_cast(buffer_->data()) : nullptr; } + [[nodiscard]] inline const_iterator cbegin() const { return begin(); } + [[nodiscard]] inline iterator end() { return buffer_ ? static_cast(buffer_->data()) + size() : nullptr; } + [[nodiscard]] inline const_iterator end() const { return buffer_ ? static_cast(buffer_->data()) + size() : nullptr; } + [[nodiscard]] inline const_iterator cend() const { return end(); } + [[nodiscard]] inline reference at(size_type pos) { + MIJIN_ASSERT(pos < size(), "BufferView::at(): pos is out of range."); + return *(begin() + pos); + } + [[nodiscard]] inline const_reference at(size_type pos) const { + MIJIN_ASSERT(pos < size(), "BufferView::at(): pos is out of range."); + return *(begin() + pos); + } + // operators + inline reference operator[](size_type pos) { return at(pos); } + inline const_reference operator[](size_type pos) const { return at(pos); } +}; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED) diff --git a/source/mijin/debug/assert.hpp b/source/mijin/debug/assert.hpp new file mode 100644 index 0000000..e5349d2 --- /dev/null +++ b/source/mijin/debug/assert.hpp @@ -0,0 +1,146 @@ + +#pragma once + +#if !defined(MIJIN_DEBUG_ASSERT_HPP_INCLUDED) +#define MIJIN_DEBUG_ASSERT_HPP_INCLUDED 1 + +#include +#include + +namespace mijin +{ + +// +// public defines +// + +#if MIJIN_DEBUG +#ifdef _WIN32 +#pragma comment(lib, "kernel32") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +#define MIJIN_TRAP() DebugBreak() +#define MIJIN_FUNC() __FUNCSIG__ +#else // _WIN32 +#include +#define MIJIN_TRAP() raise(SIGTRAP) +#define MIJIN_FUNC() "" // TODO: __PRETTY_FUNCTION__ is not working for some reason -.- +#endif // !_WIN32 + +#define MIJIN_DO_RAISE_ERROR(msg, func, file, line) \ +switch (mijin::handleError(msg, func, file, line)) \ +{ \ + case mijin::ErrorHandling::CONTINUE: \ + break; \ + case mijin::ErrorHandling::TRAP: \ + MIJIN_TRAP(); \ + [[fallthrough]]; \ + default: /* ABORT */ \ + std::abort(); \ +} + +#define MIJIN_RAISE_ERROR(msg, func, file, line) MIJIN_DO_RAISE_ERROR(msg, func, file, line) + +#define MIJIN_ERROR(msg) \ +MIJIN_RAISE_ERROR(msg, MIJIN_FUNC(), __FILE__, __LINE__) + +#define MIJIN_FATAL(msg) \ +MIJIN_ERROR(msg) \ +std::abort() + +// TODO: make ignoreAll work (static variables cannot be used in constexpr functions) +#define MIJIN_ASSERT(condition, msg) \ +if (!static_cast(condition)) \ +{ \ + /* static bool ignoreAll = false; */ \ + if (true) /*!ignoreAll */ \ + { \ + mijin::AssertionResult assertion_result__ = mijin::handleAssert(#condition,\ + msg, MIJIN_FUNC(), __FILE__, __LINE__); \ + switch (assertion_result__) \ + { \ + case mijin::AssertionResult::ABORT: \ + std::abort(); \ + break; \ + case mijin::AssertionResult::IGNORE: \ + break; \ + case mijin::AssertionResult::IGNORE_ALL: \ + /* ignoreAll = true; */ \ + break; \ + default: /* ERROR */ \ + MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \ + break; \ + } \ + } \ +} + +#define MIJIN_ASSERT_FATAL(condition, msg) \ +if (!static_cast(condition)) \ +{ \ + MIJIN_ERROR("Debug assertion failed: " #condition "\nMessage: " msg); \ +} +#else // MIJIN_DEBUG +#define MIJIN_ERROR(...) +#define MIJIN_FATAL(...) std::abort() +#define MIJIN_ASSERT(...) +#define MIJIN_ASSERT_FATAL(...) +#endif // !MIJIN_DEBUG + +// +// public constants +// + +// +// public types +// + +enum class AssertionResult +{ + ABORT = -1, // call std::abort() + ERROR = 0, // raise an error using MIJIN_ERROR() handling + IGNORE = 1, // do nothing + IGNORE_ALL = 2 // do nothing and nevery halt again (not implemented yet) +}; + +enum class ErrorHandling +{ + TRAP = 0, + CONTINUE = 1, + ABORT = 2 +}; + +// +// public functions +// + +#ifdef MIJIN_USE_CUSTOM_ASSERTION_HANDLER +AssertionResult handleAssert(const char* condition, + const char* message, const char* function, + const char* file, int line) noexcept; +#else +constexpr AssertionResult handleAssert(const char* /* condition */, + const char* /* message */, const char* /* function */, + const char* /* file */, int /* line */) +{ + return AssertionResult::ERROR; +} +#endif // MIJIN_USE_CUSTOM_ASSERTION_HANDLER + +#ifdef MIJIN_USE_CUSTOM_ERROR_HANDLER +ErrorHandling handleError(const char* message, const char* function, + const char* file, int line) noexcept; +#else +inline ErrorHandling handleError(const char* message, const char* function, + const char* file, int line) noexcept +{ + std::puts(message); + std::printf("Function: %s\n", function); + std::printf("File: %s\n", file); + std::printf("Line: %d\n", line); + (void) std::fflush(stdout); + return ErrorHandling::TRAP; +} +#endif + +} // namespace mijin + +#endif // !defined(MIJIN_DEBUG_ASSERT_HPP_INCLUDED) diff --git a/source/mijin/debug/symbol_info.cpp b/source/mijin/debug/symbol_info.cpp new file mode 100644 index 0000000..fb6312b --- /dev/null +++ b/source/mijin/debug/symbol_info.cpp @@ -0,0 +1,87 @@ + +#include "symbol_info.hpp" + +#include +#include "../detect.hpp" + +#if MIJIN_TARGET_OS == MIJIN_OS_LINUX +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif +#include +#endif + +#if MIJIN_COMPILER == MIJIN_COMPILER_GCC +#include +#endif + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +static thread_local std::unordered_map g_functionNameCache; + +// +// internal functions +// + +// +// public functions +// + +const char* lookupFunctionName(const void* function) +{ + auto it = g_functionNameCache.find(function); + if (it != g_functionNameCache.end()) { + return it->second.c_str(); + } + +#if MIJIN_TARGET_OS == MIJIN_OS_LINUX + Dl_info info; + dladdr(function, &info); + + std::string name = demangleCPPIdentifier(info.dli_sname); + if (name.empty()) { + name = info.dli_sname; + } +#else + std::string name = ""; +#endif + return g_functionNameCache.insert({function, std::move(name)}).first->second.c_str(); +} + +#if MIJIN_COMPILER == MIJIN_COMPILER_GCC +std::string demangleCPPIdentifier(const char* identifier) +{ + char* demangled = abi::__cxa_demangle(identifier, /* output_buffer = */ nullptr, /* length = */ nullptr, /* status = */ nullptr); + if (demangled != nullptr) + { + std::string name = demangled; + std::free(demangled); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + return name; + } + return ""; +} +#else +std::string demangleCPPIdentifier(const char* /* identifier */) +{ + return ""; +} +#endif + +} // namespace mijin diff --git a/source/mijin/debug/symbol_info.hpp b/source/mijin/debug/symbol_info.hpp new file mode 100644 index 0000000..3421052 --- /dev/null +++ b/source/mijin/debug/symbol_info.hpp @@ -0,0 +1,33 @@ + +#pragma once + +#if !defined(MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED) +#define MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED 1 + +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +// +// public functions +// + +[[nodiscard]] const char* lookupFunctionName(const void* function); +[[nodiscard]] std::string demangleCPPIdentifier(const char* identifier); + +} // namespace mijin + +#endif // !defined(MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED) diff --git a/source/mijin/detect.hpp b/source/mijin/detect.hpp new file mode 100644 index 0000000..cc38e80 --- /dev/null +++ b/source/mijin/detect.hpp @@ -0,0 +1,78 @@ + +#pragma once + +#if !defined(MIJIN_DETECT_HPP_INCLUDED) +#define MIJIN_DETECT_HPP_INCLUDED 1 + +namespace mijin +{ + +// +// public defines +// + +#if !defined(__has_include) + #define __has_include(x) (false) +#endif + +#define MIJIN_OS_UNKNOWN 0 +#define MIJIN_OS_LINUX 1 +#define MIJIN_OS_WINDOWS 2 +#define MIJIN_OS_OSX 3 + +#if !defined(MIJIN_TARGET_OS) + #if defined(__linux__) + #define MIJIN_TARGET_OS MIJIN_OS_LINUX + #elif defined(_WIN32) + #define MIJIN_TARGET_OS MIJIN_OS_WINDOWS + #else // TODO: macos + #define MIJIN_TARGET_OS MIJIN_OS_UNKNOWN + #endif +#endif + +#define MIJIN_COMPILER_UNKNOWN 0 +#define MIJIN_COMPILER_GCC 1 +#define MIJIN_COMPILER_CLANG 2 +#define MIJIN_COMPILER_MSVC 3 + +#if !defined(MIJIN_COMPILER) + #if defined(__clang__) + #define MIJIN_COMPILER MIJIN_COMPILER_CLANG + #elif defined(__GNUC__) + #define MIJIN_COMPILER MIJIN_COMPILER_GCC + #elif defined(_MSC_VER) + #define MIJIN_COMPILER MIJIN_COMPILER_MSVC + #else + #define MIJIN_COMPILER MIJIN_COMPILER_UNKNOWN + #endif +#endif + +#define MIJIN_STDLIB_UNKNOWN 0 +#define MIJIN_STDLIB_GLIBC 1 + +#if !defined(MIJIN_STDLIB) + #if __has_include() + #include + #endif + #if defined(__GLIBC__) + #define MIJIN_STDLIB MIJIN_STDLIB_GLIBC + #else + #define MIJIN_STDLIB MIJIN_STDLIB_UNKNOWN + #endif +#endif + +// +// public constants +// + +// +// public types +// + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_DETECT_HPP_INCLUDED) diff --git a/source/mijin/io/stream.cpp b/source/mijin/io/stream.cpp new file mode 100644 index 0000000..284c897 --- /dev/null +++ b/source/mijin/io/stream.cpp @@ -0,0 +1,308 @@ + +#include "./stream.hpp" + +#include +#include +#include + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +// +// internal functions +// + +// +// public functions +// + +void Stream::flush() {} + +StreamError Stream::readString(std::string& outString) +{ + std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables) + StreamError error = read(length); + if (error != StreamError::SUCCESS) { + return error; + } + + std::string result; + result.resize(length); + error = readSpan(result.begin(), result.end()); + if (error != StreamError::SUCCESS) { + return error; + } + outString = std::move(result); + return StreamError::SUCCESS; +} + +StreamError Stream::writeString(std::string_view str) +{ + assert(str.length() <= std::numeric_limits::max()); + const std::uint32_t length = static_cast(str.length()); + StreamError error = write(length); + if (error != StreamError::SUCCESS) { + return error; + } + return writeSpan(str.begin(), str.end()); +} + +FileStream::~FileStream() +{ + if (handle) { + close(); + } +} + +StreamError FileStream::open(const char* path, FileOpenMode mode_) +{ + mode = mode_; + + const char* modeStr; // NOLINT(cppcoreguidelines-init-variables) + switch (mode_) + { + case FileOpenMode::READ: + modeStr = "rb"; + break; + case FileOpenMode::WRITE: + modeStr = "wb"; + break; + case FileOpenMode::APPEND: + modeStr = "ab"; + break; + case FileOpenMode::READ_WRITE: + modeStr = "r+b"; + break; + } + handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory) + if (!handle && mode_ == FileOpenMode::READ_WRITE) { + handle = std::fopen(path, "w+b"); // NOLINT(cppcoreguidelines-owning-memory) + } + if (!handle) { + return StreamError::IO_ERROR; + } + + int result = std::fseek(handle, 0, SEEK_END); + assert(result == 0); + length = std::ftell(handle); + result = std::fseek(handle, 0, SEEK_SET); + assert(result == 0); + + return StreamError::SUCCESS; +} + +void FileStream::close() +{ + assert(handle); + const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory) + assert(result == 0); +} + +StreamError FileStream::readRaw(std::span buffer, bool partial, std::size_t* outBytesRead) +{ + assert(handle); + assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE); + + const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle); + if (std::ferror(handle)) { + return StreamError::IO_ERROR; + } + if (!partial && readBytes < buffer.size()) { + return StreamError::IO_ERROR; + } + if (outBytesRead != nullptr) { + *outBytesRead = readBytes; + } + return StreamError::SUCCESS; +} + +StreamError FileStream::writeRaw(std::span buffer) +{ + assert(handle); + assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE); + + const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle); + if (written != buffer.size() || std::ferror(handle)) { + return StreamError::IO_ERROR; + } + + length = std::max(length, std::ftell(handle)); + return StreamError::SUCCESS; +} + +std::size_t FileStream::tell() +{ + assert(handle); + + return std::ftell(handle); +} + +StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode) +{ + assert(handle); + + int origin; // NOLINT(cppcoreguidelines-init-variables) + switch (seekMode) + { + case SeekMode::ABSOLUTE: + origin = SEEK_SET; + break; + case SeekMode::RELATIVE: + origin = SEEK_CUR; + break; + case SeekMode::RELATIVE_TO_END: + origin = SEEK_END; + break; + } + const int result = std::fseek(handle, static_cast(pos), origin); + if (result != 0 || std::ferror(handle)) { + return StreamError::IO_ERROR; + } + return StreamError::SUCCESS; +} + +void FileStream::flush() +{ + const int result = std::fflush(handle); + assert(result == 0); +} + +bool FileStream::isAtEnd() +{ + assert(handle); + + (void) std::fgetc(handle); + if (std::feof(handle)) { + return true; + } + const int result = std::fseek(handle, -1, SEEK_CUR); + assert(result == 0); + return false; +} + +StreamFeatures FileStream::getFeatures() +{ + if (handle) + { + return { + .read = (mode == FileOpenMode::READ), + .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE), + .tell = true, + .seek = true + }; + } + return {}; +} + +void MemoryStream::openRW(std::span data) +{ + assert(!isOpen()); + data_ = data; + pos_ = 0; + canWrite_ = true; +} + +void MemoryStream::openRO(std::span data) +{ + assert(!isOpen()); + data_ = std::span(const_cast(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-const-cast) we'll be fine + pos_ = 0; + canWrite_ = false; +} + +void MemoryStream::close() +{ + assert(isOpen()); + data_ = {}; +} + +StreamError MemoryStream::readRaw(std::span buffer, bool partial, std::size_t* outBytesRead) +{ + assert(isOpen()); + if (!partial && availableBytes() < buffer.size()) { + return StreamError::IO_ERROR; // TODO: need more errors? + } + const std::size_t numBytes = std::min(buffer.size(), availableBytes()); + std::copy_n(data_.begin() + static_cast(pos_), numBytes, buffer.begin()); + if (outBytesRead) { + *outBytesRead = numBytes; + } + pos_ += numBytes; + return StreamError::SUCCESS; +} + +StreamError MemoryStream::writeRaw(std::span buffer) +{ + assert(isOpen()); + assert(canWrite_); + + if (availableBytes() < buffer.size()) { + return StreamError::IO_ERROR; + } + std::copy(buffer.begin(), buffer.end(), data_.begin() + static_cast(pos_)); + pos_ += buffer.size(); + return StreamError::SUCCESS; +} + +std::size_t MemoryStream::tell() +{ + assert(isOpen()); + return pos_; +} + +StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode) +{ + assert(isOpen()); + std::intptr_t newPos = -1; + switch (seekMode) + { + case SeekMode::ABSOLUTE: + newPos = pos; + break; + case SeekMode::RELATIVE: + newPos = static_cast(pos_) + pos; + break; + case SeekMode::RELATIVE_TO_END: + newPos = static_cast(data_.size()) + pos; + break; + } + if (newPos < 0 || newPos > data_.size()) { + return StreamError::IO_ERROR; + } + pos_ = newPos; + return StreamError::SUCCESS; +} + +bool MemoryStream::isAtEnd() +{ + assert(isOpen()); + return pos_ == data_.size(); +} + +StreamFeatures MemoryStream::getFeatures() +{ + return { + .read = true, + .write = canWrite_, + .tell = true, + .seek = true + }; +} + +} // namespace mijin diff --git a/source/mijin/io/stream.hpp b/source/mijin/io/stream.hpp new file mode 100644 index 0000000..64180b3 --- /dev/null +++ b/source/mijin/io/stream.hpp @@ -0,0 +1,181 @@ + +#pragma once + +#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED) +#define MIJIN_IO_STREAM_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +enum class SeekMode +{ + ABSOLUTE, + RELATIVE, + RELATIVE_TO_END +}; + +struct StreamFeatures +{ + bool read : 1 = false; + bool write : 1 = false; + bool tell : 1 = false; + bool seek : 1 = false; +}; + +enum class FileOpenMode +{ + READ, + WRITE, + APPEND, + READ_WRITE +}; + +enum class StreamError +{ + SUCCESS, + IO_ERROR, + UNKNOWN_ERROR +}; + +class Stream +{ +public: + virtual ~Stream() = default; + +public: + virtual StreamError readRaw(std::span buffer, bool partial = false, std::size_t* outBytesRead = nullptr) = 0; + virtual StreamError writeRaw(std::span buffer) = 0; + virtual std::size_t tell() = 0; + virtual StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) = 0; + virtual void flush(); + virtual bool isAtEnd() = 0; + virtual StreamFeatures getFeatures() = 0; + + inline StreamError readRaw(void* outData, std::size_t bytes, bool partial = false, std::size_t* outBytesRead = nullptr) + { + std::uint8_t* ptr = static_cast(outData); + return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead); + } + + inline StreamError writeRaw(const void* data, std::size_t bytes) + { + const std::uint8_t* ptr = static_cast(data); + return writeRaw(std::span(ptr, ptr + bytes)); + } + + template + inline StreamError read(T& value) + { + return readRaw(&value, sizeof(T)); + } + + template + inline StreamError readSpan(T& values) + { + auto asSpan = std::span(values); + return readRaw(asSpan.data(), asSpan.size_bytes()); + } + + template + inline StreamError readSpan(TItBegin&& begin, TItEnd&& end) + { + auto asSpan = std::span(std::forward(begin), std::forward(end)); + return readRaw(asSpan.data(), asSpan.size_bytes()); + } + + template + inline StreamError write(const T& value) + { + return writeRaw(&value, sizeof(T)); + } + + template + inline StreamError writeSpan(const T& values) + { + auto asSpan = std::span(values); + return writeRaw(asSpan.data(), asSpan.size_bytes()); + } + + template + inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end) + { + return writeSpan(std::span(std::forward(begin), std::forward(end))); + } + + StreamError readString(std::string& outString); + StreamError writeString(std::string_view str); +}; + +class FileStream : public Stream +{ +private: + std::FILE* handle = nullptr; // TODO: wrap in gsl::owner<> + FileOpenMode mode; + std::size_t length = 0; +public: + ~FileStream() override; + + StreamError open(const char* path, FileOpenMode mode_); + void close(); + [[nodiscard]] inline bool isOpen() const { return handle != nullptr; } + + // Stream overrides + StreamError readRaw(std::span buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override; + StreamError writeRaw(std::span buffer) override; + std::size_t tell() override; + StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; + void flush() override; + bool isAtEnd() override; + StreamFeatures getFeatures() override; +}; + +class MemoryStream : public Stream +{ +private: + std::span data_; + std::size_t pos_ = 0; + bool canWrite_ = false; +public: + void openRW(std::span data); + void openRO(std::span data); + void close(); + [[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; } + [[nodiscard]] inline std::size_t availableBytes() const { + assert(isOpen()); + return data_.size() - pos_; + } + + // Stream overrides + StreamError readRaw(std::span buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override; + StreamError writeRaw(std::span buffer) override; + std::size_t tell() override; + StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; + bool isAtEnd() override; + StreamFeatures getFeatures() override; +}; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED) diff --git a/source/mijin/memory/data_pool.cpp b/source/mijin/memory/data_pool.cpp new file mode 100644 index 0000000..becc6c3 --- /dev/null +++ b/source/mijin/memory/data_pool.cpp @@ -0,0 +1,31 @@ + +#include "data_pool.hpp" + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +// +// internal functions +// + +// +// public functions +// + +} // namespace mijin diff --git a/source/mijin/memory/data_pool.hpp b/source/mijin/memory/data_pool.hpp new file mode 100644 index 0000000..b0f1972 --- /dev/null +++ b/source/mijin/memory/data_pool.hpp @@ -0,0 +1,90 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED) +#define MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +struct DataPool +{ + static_assert(PAGE_SIZE % alignof(std::max_align_t) == 0); + + struct alignas(std::max_align_t) Page : std::array {}; + + using page_ptr_t = std::unique_ptr; + + std::vector pages; + std::size_t offset = 0; + + DataPool() = default; + DataPool(const DataPool&) = delete; + DataPool(DataPool&&) noexcept = default; + + DataPool& operator=(const DataPool&) = delete; + DataPool& operator=(DataPool&&) noexcept = default; + + void* allocate(std::size_t bytes, std::size_t alignment = 1); + inline void reset() { offset = 0; } + + template + T* allocateAs(std::size_t elements = 1) { + return static_cast(allocate(sizeof(T) * elements, alignof(T))); + } +}; + +// +// public functions +// + +template +void* DataPool::allocate(std::size_t bytes, std::size_t alignment) +{ + assert(bytes > 0 && bytes <= PAGE_SIZE); + assert(alignment > 0 && alignment <= alignof(std::max_align_t)); + + if (offset % alignment != 0) { + offset += alignment - (offset % alignment); + } + + const std::size_t remainingOnPage = PAGE_SIZE - (offset % PAGE_SIZE); + const std::size_t page = offset / PAGE_SIZE; + const std::size_t localOffset = offset % PAGE_SIZE; + if (remainingOnPage == PAGE_SIZE || remainingOnPage < bytes) + { + // next page + if (page + 1 >= pages.size()) { + pages.push_back(std::make_unique()); + } + offset = PAGE_SIZE * (pages.size() - 1); + } + std::uint8_t* result = &(*pages[page])[localOffset]; + offset += bytes; + assert(reinterpret_cast(result) % alignment == 0); + return result; +} + +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED) diff --git a/source/mijin/types/name.cpp b/source/mijin/types/name.cpp new file mode 100644 index 0000000..15562bc --- /dev/null +++ b/source/mijin/types/name.cpp @@ -0,0 +1,128 @@ + +#include "./name.hpp" + +#include +#include +#include +#include +#include +#include + +// note: the implementation assumes that std::vector moves the strings on resize and that strings don't reallocate when moved +// while that should be the case for any sane STL implementation it may cause dangling pointers if it's not + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +// +// internal functions +// + +static std::vector& getGlobalNames() +{ + static std::vector names; + return names; +} + +static std::vector& getLocalNames() +{ + static thread_local std::vector names; + return names; +} + +static std::shared_mutex& getGlobalNamesMutex() +{ + static std::shared_mutex mutex; + return mutex; +} + +static std::unordered_map& getLocalNameCache() +{ + static std::unordered_map cache; + return cache; +} + +static void copyNamesToLocal() +{ + const std::size_t oldSize = getLocalNames().size(); + getLocalNames().resize(getGlobalNames().size()); + std::copy(getGlobalNames().begin() + static_cast(oldSize), getGlobalNames().end(), getLocalNames().begin() + static_cast(oldSize)); +} + +static std::size_t getOrCreateStringID(std::string_view string) +{ + // 1. check the cache + auto it = getLocalNameCache().find(std::string(string)); + if (it != getLocalNameCache().end()) { + return it->second; + } + + // 2. check the local state + for (std::size_t idx = 0; idx < getLocalNames().size(); ++idx) + { + if (getLocalNames()[idx] == string) + { + getLocalNameCache()[std::string(string)] = idx; + return idx; + } + } + + // 3. copy global state and check local again + { + std::shared_lock lock(getGlobalNamesMutex()); + copyNamesToLocal(); + } + + for (std::size_t idx = 0; idx < getLocalNames().size(); ++idx) + { + if (getLocalNames()[idx] == string) + { + getLocalNameCache()[std::string(string)] = idx; + return idx; + } + } + + // 4. insert a new ID + std::unique_lock lock(getGlobalNamesMutex()); + const std::size_t idx = getGlobalNames().size(); + getGlobalNames().emplace_back(string); + copyNamesToLocal(); + getLocalNameCache()[std::string(string)] = idx; + + return idx; +} + +// +// public functions +// + +Name::Name(std::string_view string) : id_(getOrCreateStringID(string)) +{ + +} + +std::string_view Name::stringView() const +{ + if (id_ == std::numeric_limits::max()) { + return {}; + } + return getLocalNames().at(id_); +} + +} // namespace mijin diff --git a/source/mijin/types/name.hpp b/source/mijin/types/name.hpp new file mode 100644 index 0000000..74601b0 --- /dev/null +++ b/source/mijin/types/name.hpp @@ -0,0 +1,63 @@ + +#pragma once + +#if !defined(MIJIN_TYPES_NAME_HPP_INCLUDED) +#define MIJIN_TYPES_NAME_HPP_INCLUDED 1 + +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +class Name +{ +private: + std::size_t id_ = std::numeric_limits::max(); +public: + Name() = default; + Name(const Name&) = default; + Name(std::string_view string); + Name(const char* cStr) : Name(std::string_view(cStr)) {} + + Name& operator=(const Name&) = default; + auto operator<=>(const Name&) const = default; + + [[nodiscard]] std::string_view stringView() const; + [[nodiscard]] const char* c_str() const { + return stringView().data(); + } + + [[nodiscard]] std::size_t getID() const { return id_; } +}; + +// +// public functions +// + +} // namespace mijin + +namespace std +{ +template<> +struct hash : hash +{ + std::size_t operator()(mijin::Name name) const noexcept { + return hash::operator()(name.getID()); + } +}; +} + +#endif // !defined(MIJIN_TYPES_NAME_HPP_INCLUDED) diff --git a/source/mijin/util/bitarray.hpp b/source/mijin/util/bitarray.hpp new file mode 100644 index 0000000..9e17728 --- /dev/null +++ b/source/mijin/util/bitarray.hpp @@ -0,0 +1,75 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_BITARRAY_HPP_INCLUDED) +#define MIJIN_UTIL_BITARRAY_HPP_INCLUDED 1 + +#include +#include +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + + +template +struct BitArray +{ +private: + using byte_type = std::conditional_t; + std::array bytes; +public: + [[nodiscard]] bool get(std::size_t index) const { + assert(index < numBits); + return (bytes[index / 8] & (1 << (index % 8))); + } + void set(std::size_t index, bool value) + { + byte_type& byte = bytes[index / 8]; + const std::uint8_t mask = (1 << (index % 8)); + if constexpr (threadSafe) + { + std::uint8_t oldByte = byte.load(std::memory_order_relaxed); + std::uint8_t newByte = 0; + do + { + if (value) { + newByte = oldByte | mask; + } + else { + newByte = oldByte & ~mask; + } + } while (!byte.compare_exchange_weak(oldByte, newByte, std::memory_order_release, std::memory_order_relaxed)); + } + else + { + if (value) { + byte |= mask; + } + else { + byte &= ~mask; + } + } + } +}; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_BITARRAY_HPP_INCLUDED) diff --git a/source/mijin/util/bitflags.hpp b/source/mijin/util/bitflags.hpp new file mode 100644 index 0000000..986c7fa --- /dev/null +++ b/source/mijin/util/bitflags.hpp @@ -0,0 +1,92 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED) +#define MIJIN_UTIL_BITFLAGS_HPP_INCLUDED 1 + +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +struct BitFlags +{ + constexpr TBits& operator |=(const BitFlags& other) { + for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) { + *(std::bit_cast(asBits()) + idx) |= *(std::bit_cast(other.asBits()) + idx); + } + return *asBits(); + } + constexpr TBits& operator &=(const BitFlags& other) { + for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) { + *(std::bit_cast(asBits()) + idx) &= *(std::bit_cast(other.asBits()) + idx); + } + return *asBits(); + } + + constexpr TBits& operator ^=(const BitFlags& other) { + for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) { + *(std::bit_cast(asBits()) + idx) ^= *(std::bit_cast(other.asBits()) + idx); + } + return *asBits(); + } + + constexpr TBits operator& (const BitFlags& other) const + { + TBits copy(*asBits()); + copy &= other; + return copy; + } + + constexpr TBits operator| (const BitFlags& other) const + { + TBits copy(*asBits()); + copy |= other; + return copy; + } + + constexpr TBits operator^ (const BitFlags& other) const + { + TBits copy(*asBits()); + copy ^= other; + return copy; + } + + constexpr operator bool() const { + for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) { + if (*(std::bit_cast(asBits()) + idx) != std::byte(0)) { + return true; + } + } + return false; + } + + constexpr bool operator!() const { + return !static_cast(*this); + } +private: + constexpr TBits* asBits() { return static_cast(this); } + constexpr const TBits* asBits() const { return static_cast(this); } +}; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED) diff --git a/source/mijin/util/concepts.hpp b/source/mijin/util/concepts.hpp new file mode 100644 index 0000000..e40c4a1 --- /dev/null +++ b/source/mijin/util/concepts.hpp @@ -0,0 +1,48 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_CONCEPTS_HPP_INCLUDED) +#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1 + +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +concept standard_type = std::is_standard_layout_v; + +template +concept trivial_type = std::is_trivial_v; + +template +concept enum_type = std::is_enum_v; + +template +concept arithmetic_type = std::is_arithmetic_v; + +template +concept pointer_type = std::is_pointer_v; + +template +concept reference_type = std::is_reference_v; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_CONCEPTS_HPP_INCLUDED) diff --git a/source/mijin/util/flag.hpp b/source/mijin/util/flag.hpp new file mode 100644 index 0000000..0a4609f --- /dev/null +++ b/source/mijin/util/flag.hpp @@ -0,0 +1,161 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_FLAG_HPP_INCLUDED) +#define MIJIN_UTIL_FLAG_HPP_INCLUDED 1 + +#include +#include "./traits.hpp" +#include "./types.hpp" + +namespace mijin +{ + +// +// public defines +// + +#define MIJIN_DEFINE_FLAG(name) \ +struct name : Flag \ +{ \ +private: \ + struct Proxy_ { \ + uint8_t value; \ + }; \ +public: \ + constexpr name() = default; \ + constexpr name(const name&) = default; \ + constexpr name(Proxy_ proxy) \ + : Flag(proxy.value) {} \ + constexpr explicit name(bool value) noexcept \ + : Flag(value) {} \ + name& operator=(const name&) = default; \ + static constexpr Proxy_ YES{1}; \ + static constexpr Proxy_ NO{0}; \ +} + +// +// public constants +// + +// +// public types +// + +struct Flag +{ + std::uint8_t value; + + Flag() = default; + Flag(const Flag&) = default; + explicit constexpr Flag(uint8_t value) noexcept : value(value) {} + + Flag& operator=(const Flag&) = default; + + constexpr bool operator ==(const Flag& other) const noexcept + { + return value == other.value; + } + constexpr bool operator !=(const Flag& other) const noexcept + { + return value != other.value; + } + constexpr bool operator !() const noexcept + { + return !value; + } + constexpr operator bool() const noexcept + { + return value != 0; + } +}; + +template +concept FlagType = requires (T& value) +{ + value = T::YES; + value = T::NO; +}; + +namespace impl +{ +template +class FlagSetStorage; + +template +class FlagSetStorage : public FlagSetStorage +{ +private: + using base_t = FlagSetStorage; + static constexpr typename base_t::data_t BIT = (1 << offset); +public: + constexpr void set(TFirst value) noexcept + { + if (value) + { + base_t::data_ |= BIT; + } + else + { + base_t::data_ &= ~BIT; + } + } + constexpr bool get(TFirst) noexcept + { + return (base_t::data_ & BIT) != 0; + } +}; + +template +class FlagSetStorage +{ +protected: + using data_t = uint_next_t; +protected: + data_t data_ = data_t(0); +}; +} // namespace impl + +template +class FlagSet +{ +private: + using storage_t = impl::FlagSetStorage<0, TFlags...>; +private: + storage_t storage_; +public: + FlagSet() = default; + FlagSet(const FlagSet&) = default; + template + constexpr FlagSet(TFlags2... flags) + { + (set(flags), ...); + } +public: + FlagSet& operator=(const FlagSet&) = default; + template requires is_any_type_v + FlagSet& operator=(T flag) noexcept + { + reset(flag); + return *this; + } + template requires is_any_type_v + FlagSet& operator|=(T flag) noexcept + { + set(flag); + return *this; + } + template requires is_any_type_v + FlagSet& operator&=(T flag) noexcept + { + unset(flag); + } +}; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_FLAG_HPP_INCLUDED) diff --git a/source/mijin/util/os.cpp b/source/mijin/util/os.cpp new file mode 100644 index 0000000..2632324 --- /dev/null +++ b/source/mijin/util/os.cpp @@ -0,0 +1,47 @@ + +#include "os.hpp" + +#include "../detect.hpp" + +#if MIJIN_TARGET_OS == MIJIN_OS_LINUX + #include +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS +// TODO +#endif + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +// +// internal functions +// + +// +// public functions +// + +void setCurrentThreadName(const char* threadName) +{ +#if MIJIN_TARGET_OS == MIJIN_OS_LINUX + pthread_setname_np(pthread_self(), threadName); +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS +#endif +} + +} // namespace mijin diff --git a/source/mijin/util/os.hpp b/source/mijin/util/os.hpp new file mode 100644 index 0000000..1cb2a9b --- /dev/null +++ b/source/mijin/util/os.hpp @@ -0,0 +1,30 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_OS_HPP_INCLUDED) +#define MIJIN_UTIL_OS_HPP_INCLUDED 1 + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +// +// public functions +// + +void setCurrentThreadName(const char* threadName); + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_OS_HPP_INCLUDED) diff --git a/source/mijin/util/string.hpp b/source/mijin/util/string.hpp new file mode 100644 index 0000000..ab3b763 --- /dev/null +++ b/source/mijin/util/string.hpp @@ -0,0 +1,52 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) +#define MIJIN_UTIL_STRING_HPP_INCLUDED 1 + +#include +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +// +// public functions +// + +template +std::string join(const TRange& elements, const char* const delimiter) +{ + std::ostringstream oss; + auto first = std::begin(elements); + auto last = std::end(elements); + + if (first != last) + { + std::copy(first, std::prev(last), std::ostream_iterator(oss, delimiter)); + first = prev(last); + } + if (first != last) + { + oss << *first; + } + + return oss.str(); +} + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) diff --git a/source/mijin/util/template_string.hpp b/source/mijin/util/template_string.hpp new file mode 100644 index 0000000..8a3865a --- /dev/null +++ b/source/mijin/util/template_string.hpp @@ -0,0 +1,126 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED) +#define MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED 1 + +#include +#include "../detect.hpp" + +namespace mijin +{ + +// +// public defines +// + +// #define MIJIN_MAKE_TEMPLATE_STRING(str) decltype(str ## _mijin_template_string) +#define MIJIN_MAKE_TEMPLATE_STRING(str) \ + mijin::impl::template_string_builder< \ + mijin::impl::charAt<0>(str), \ + mijin::impl::charAt<1>(str), \ + mijin::impl::charAt<2>(str), \ + mijin::impl::charAt<3>(str), \ + mijin::impl::charAt<4>(str), \ + mijin::impl::charAt<5>(str), \ + mijin::impl::charAt<6>(str), \ + mijin::impl::charAt<7>(str), \ + mijin::impl::charAt<8>(str), \ + mijin::impl::charAt<9>(str), \ + mijin::impl::charAt<10>(str), \ + mijin::impl::charAt<11>(str), \ + mijin::impl::charAt<12>(str), \ + mijin::impl::charAt<13>(str), \ + mijin::impl::charAt<14>(str), \ + mijin::impl::charAt<15>(str), \ + mijin::impl::charAt<16>(str), \ + mijin::impl::charAt<17>(str), \ + mijin::impl::charAt<18>(str), \ + mijin::impl::charAt<19>(str), \ + mijin::impl::charAt<20>(str), \ + mijin::impl::charAt<21>(str), \ + mijin::impl::charAt<22>(str), \ + mijin::impl::charAt<23>(str), \ + mijin::impl::charAt<24>(str), \ + mijin::impl::charAt<25>(str), \ + mijin::impl::charAt<26>(str), \ + mijin::impl::charAt<27>(str), \ + mijin::impl::charAt<28>(str), \ + mijin::impl::charAt<29>(str) \ + > + +// +// public constants +// + +template +using template_string = std::integer_sequence; + +namespace impl +{ +template +struct TemplateStringHelper; + +template +struct TemplateStringHelper> +{ + static constexpr const char value[sizeof...(chars) + 1] = {chars..., '\0'}; // NOLINT +}; + +template +constexpr template_string prependToString(template_string) +{ + return {}; +} + +template +struct TemplateStringBuilder {}; + +template +struct TemplateStringBuilder +{ + using previous_seq_t = typename TemplateStringBuilder::seq_t; + using seq_t = decltype(prependToString(previous_seq_t())); +}; + +template +struct TemplateStringBuilder<'\0', chars...> +{ + using seq_t = template_string<>; +}; + +template +constexpr char charAt(const char (&text)[len]) +{ + if constexpr (pos < len) { + return text[pos]; + } + else { + return '\0'; + } +} + +template +typename TemplateStringBuilder::seq_t buildTemplateString() +{ + return {}; +} + +template +using template_string_builder = typename TemplateStringBuilder::seq_t; +} + +template +inline constexpr auto template_string_v = impl::TemplateStringHelper::value; + +// +// public types +// + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED) diff --git a/source/mijin/util/traits.hpp b/source/mijin/util/traits.hpp new file mode 100644 index 0000000..dfe4303 --- /dev/null +++ b/source/mijin/util/traits.hpp @@ -0,0 +1,118 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED) +#define MIJIN_UTIL_TRAITS_HPP_INCLUDED 1 + +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +struct always_false +{ + static constexpr bool value = false; +}; + +template +inline constexpr bool always_false_v = always_false::value; + +template +struct always_false_val +{ + static constexpr bool value = false; +}; + +template +inline constexpr bool always_false_val_v = always_false_val::value; + + + +template typename TFilter, typename... TArgs> +struct TypeFilter; + +template typename TFilter> +struct TypeFilter +{ + using type_t = std::tuple<>; +}; + +template typename TFilter, typename TFirst, typename... TArgs> +struct TypeFilter +{ + static consteval auto makeTypeHelper() + { + using base_t = typename TypeFilter::type_t; + + // note: not using decltype, as the compiler might think it is being evaluated + if constexpr (!TFilter::value) { + return static_cast(nullptr); + } + else { + auto wrapper = [](std::tuple*) + { + return static_cast*>(nullptr); + }; + return wrapper(static_cast(nullptr)); + } + } + + using type_t = std::remove_pointer_t; +}; + +template typename TFilter, typename... TArgs> +auto typeFilterHelper(std::tuple*) +{ + return static_cast::type_t*>(nullptr); +} + +template typename TFilter, typename TTuple> +using filter_types_t = std::remove_pointer_t(static_cast(nullptr)))>; + +template typename TPredicate, typename... TArgs> +auto mapTypesHelper(std::tuple) +{ + return static_cast...>*>(nullptr); +} + +template typename TPredicate, typename TTuple> +using map_types_t = std::remove_pointer_t(std::declval()))>; + +template typename TPredicate, template typename TTemplate> +struct map_template { + template + using type_t = TTemplate>; +}; + +template +struct is_any_type : std::disjunction...> {}; + +template +static constexpr bool is_any_type_v = is_any_type::value; + +// +// public functions +// + +template +decltype(auto) delayEvaluation(TType&& value) +{ + return static_cast(value); +} + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED) diff --git a/source/mijin/util/types.hpp b/source/mijin/util/types.hpp new file mode 100644 index 0000000..2760ffa --- /dev/null +++ b/source/mijin/util/types.hpp @@ -0,0 +1,127 @@ + +#pragma once + +#if !defined(MIJIN_UTIL_TYPES_HPP_INCLUDED) +#define MIJIN_UTIL_TYPES_HPP_INCLUDED 1 + +#include +#include +#include "./traits.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +namespace impl +{ +template +consteval auto getUintType() +{ + if constexpr (numBits == 8) { + return std::uint8_t{}; + } + else if constexpr (numBits == 16) { + return std::uint16_t{}; + } + else if constexpr (numBits == 32) { + return std::uint32_t{}; + } + else if constexpr (numBits == 64) { + return std::uint64_t{}; + } + else { + static_assert(always_false_val_v, "No uint type with this bit count."); + } +} + +template +consteval auto getNextUintType() +{ + if constexpr (numBits <= 8) { + return std::uint8_t{}; + } + else if constexpr (numBits <= 16) { + return std::uint16_t{}; + } + else if constexpr (numBits <= 32) { + return std::uint32_t{}; + } + else if constexpr (numBits <= 64) { + return std::uint64_t{}; + } + else { + static_assert(always_false_val_v, "No uint type with this bit count."); + } +} + +template +consteval auto getIntType() +{ + if constexpr (numBits == 8) { + return std::int8_t{}; + } + else if constexpr (numBits == 16) { + return std::int16_t{}; + } + else if constexpr (numBits == 32) { + return std::int32_t{}; + } + else if constexpr (numBits == 64) { + return std::int64_t{}; + } + else { + static_assert(always_false_val_v, "No int type with this bit count."); + } +} + +template +consteval auto getNextIntType() +{ + if constexpr (numBits <= 8) { + return std::int8_t{}; + } + else if constexpr (numBits <= 16) { + return std::int16_t{}; + } + else if constexpr (numBits <= 32) { + return std::int32_t{}; + } + else if constexpr (numBits <= 64) { + return std::int64_t{}; + } + else { + static_assert(always_false_val_v, "No int type with this bit count."); + } +} +} + +template +using uintx_t = decltype(impl::getUintType()); + +template +using uint_next_t = decltype(impl::getNextUintType()); + +template +using intx_t = decltype(impl::getIntType()); + +template +using int_next_t = decltype(impl::getNextIntType()); + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_UTIL_TYPES_HPP_INCLUDED) diff --git a/source/mijin/virtual_filesystem/filesystem.cpp b/source/mijin/virtual_filesystem/filesystem.cpp new file mode 100644 index 0000000..dbad57f --- /dev/null +++ b/source/mijin/virtual_filesystem/filesystem.cpp @@ -0,0 +1,128 @@ + +#include "./filesystem.hpp" + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +// +// internal functions +// + +// +// public functions +// + +std::vector OSFileSystemAdapter::getRoots() +{ + return { + "/" // TODO: other OSs + }; +} + +fs::path OSFileSystemAdapter::getHomeFolder() +{ + return "/home/mewin"; // very TODO +} + +std::vector OSFileSystemAdapter::listFiles(const fs::path& folder) +{ + std::vector entries; + std::error_code err; + fs::directory_iterator iterator(folder, fs::directory_options::skip_permission_denied, err); + if (err) { + return {}; // TODO: propagate? + } + for (const fs::directory_entry& entry : iterator) + { + FileInfo& info = entries.emplace_back(); + info.path = entry.path(); + info.exists = true; + info.isFolder = entry.is_directory(err); + info.isSymlink = entry.is_symlink(err); + info.isSpecial = !info.isFolder && !entry.is_regular_file(err); + info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux + if (info.isFolder) { + try { + info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator()); + } + catch(std::runtime_error&) { + info.size = 0; + } + } + else if (!info.isSpecial) + { + info.size = entry.file_size(err); + if (err) { + info.size = 0; + } + } + } + return entries; +} + +FileInfo OSFileSystemAdapter::getFileInfo(const fs::path& file) +{ + FileInfo info = {}; + std::error_code err; + info.path = file; + info.exists = fs::exists(file, err); + if (info.exists) + { + info.isFolder = fs::is_directory(file, err); + info.isSymlink = fs::is_symlink(file, err); + info.isSpecial = !info.isFolder && !fs::is_regular_file(file, err); + info.isHidden = info.path.filename().string().starts_with('.'); // at least for Linux + if (info.isFolder) { + try { + info.size = std::distance(fs::directory_iterator(info.path), fs::directory_iterator()); + } + catch(std::runtime_error&) { + info.size = 0; + } + } + else if (!info.isSpecial) + { + info.size = fs::file_size(file, err); + if (err) { + info.size = 0; + } + } + } + return info; +} + +StreamError OSFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) +{ + const std::string pathStr = path.string(); + auto stream = std::make_unique(); + const StreamError error = stream->open(pathStr.c_str(), mode); + if (error != StreamError::SUCCESS) { + return error; + } + outStream = std::move(stream); + return StreamError::SUCCESS; +} + +OSFileSystemAdapter& OSFileSystemAdapter::getInstance() // static +{ + static OSFileSystemAdapter instance; + return instance; +} + +} // namespace mijin diff --git a/source/mijin/virtual_filesystem/filesystem.hpp b/source/mijin/virtual_filesystem/filesystem.hpp new file mode 100644 index 0000000..db21c4b --- /dev/null +++ b/source/mijin/virtual_filesystem/filesystem.hpp @@ -0,0 +1,105 @@ + +#pragma once + +#if !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED) +#define MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#include +#include "../io/stream.hpp" + +namespace fs = std::filesystem; + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +struct FileInfo +{ + fs::path path; + std::size_t size = 0; + bool exists : 1 = false; + bool isFolder : 1 = false; + bool isSymlink : 1 = false; + bool isSpecial : 1 = false; + bool isHidden : 1 = false; +}; + +class FileSystemAdapter +{ +public: + virtual ~FileSystemAdapter() = default; + + [[nodiscard]] virtual std::vector getRoots() = 0; + [[nodiscard]] virtual fs::path getHomeFolder() = 0; + [[nodiscard]] virtual std::vector listFiles(const fs::path& folder) = 0; + [[nodiscard]] virtual FileInfo getFileInfo(const fs::path& file) = 0; + [[nodiscard]] virtual StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) = 0; +}; + +class OSFileSystemAdapter : public FileSystemAdapter +{ +public: + std::vector getRoots() override; + fs::path getHomeFolder() override; + std::vector listFiles(const fs::path& folder) override; + FileInfo getFileInfo(const fs::path& file) override; + StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) override; + + static OSFileSystemAdapter& getInstance(); +}; + +// +// public functions +// + +inline std::string formatFileType(const FileInfo& info) +{ + if (info.isFolder) { + return "Folder"; + } + if (info.isSpecial) { + return "Special"; + } + return "File"; +} + +inline std::string formatFileSize(std::size_t sizeInBytes) +{ + static constexpr std::array suffixes = {"bytes", "KiB", "MiB", "GiB", "TiB"}; // enough? + + if (sizeInBytes == 0) { + return "0 bytes"; + } + const std::size_t pos = std::min(static_cast(std::ceil(std::log2(sizeInBytes))) / 10, suffixes.size() - 1); + std::stringstream oss; + oss << std::setprecision(2) << std::fixed; + if (pos == 0) { + oss << sizeInBytes << " bytes"; + } + else + { + const float converted = static_cast(sizeInBytes) / std::pow(2.f, 10.f * static_cast(pos)); + oss << converted << " " << suffixes[pos]; + } + return oss.str(); +} + +} // namespace mijin + +#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED) diff --git a/source/mijin/virtual_filesystem/relative.hpp b/source/mijin/virtual_filesystem/relative.hpp new file mode 100644 index 0000000..55fcc20 --- /dev/null +++ b/source/mijin/virtual_filesystem/relative.hpp @@ -0,0 +1,87 @@ + +#pragma once + +#if !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED) +#define MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED 1 + +#include "./filesystem.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +class RelativeFileSystemAdapter : public FileSystemAdapter +{ +private: + TWrapped wrapped_; + fs::path root_; +public: + template + explicit RelativeFileSystemAdapter(fs::path root, TArgs&&... args) + : wrapped_(std::forward(args)...), root_(std::move(root)) {} + RelativeFileSystemAdapter(const RelativeFileSystemAdapter&) = default; + RelativeFileSystemAdapter(RelativeFileSystemAdapter&&) noexcept = default; + + RelativeFileSystemAdapter& operator=(const RelativeFileSystemAdapter&) = default; + RelativeFileSystemAdapter& operator=(RelativeFileSystemAdapter&&) noexcept = default; + + std::vector getRoots() override; + fs::path getHomeFolder() override; + std::vector listFiles(const fs::path& folder) override; + FileInfo getFileInfo(const fs::path& file) override; + StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) override; +}; + +// +// public functions +// + +template +std::vector RelativeFileSystemAdapter::getRoots() +{ + return { root_ }; +} + +template +fs::path RelativeFileSystemAdapter::getHomeFolder() +{ + return root_; +} + +template +std::vector RelativeFileSystemAdapter::listFiles(const fs::path& folder) +{ + std::vector result = wrapped_.listFiles(root_ / folder); + for (FileInfo& fileInfo : result) { + fileInfo.path = "/" / fileInfo.path.lexically_relative(root_); + } + return result; +} + +template +FileInfo RelativeFileSystemAdapter::getFileInfo(const fs::path& file) +{ + return wrapped_.getFileInfo(root_ / file); +} + +template +StreamError RelativeFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) +{ + return wrapped_.open(root_ / path, mode, outStream); +} + +} // namespace mijin + +#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED) diff --git a/source/mijin/virtual_filesystem/stacked.cpp b/source/mijin/virtual_filesystem/stacked.cpp new file mode 100644 index 0000000..c11eb88 --- /dev/null +++ b/source/mijin/virtual_filesystem/stacked.cpp @@ -0,0 +1,105 @@ + +#include "./stacked.hpp" + +#include + +namespace mijin +{ + +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +// +// internal variables +// + +// +// internal functions +// + +// +// public functions +// + +std::vector StackedFileSystemAdapter::getRoots() +{ + std::vector roots; + + for (auto& adapter : adapters_) + { + for (const fs::path& root : adapter->getRoots()) + { + auto it = std::find(roots.begin(), roots.end(), root); + if (it == roots.end()) { + roots.push_back(root); + } + } + } + + return roots; +} + +fs::path StackedFileSystemAdapter::getHomeFolder() +{ + if (adapters_.empty()) { + return fs::path(); + } + return adapters_.front()->getHomeFolder(); +} + +std::vector StackedFileSystemAdapter::listFiles(const fs::path& folder) +{ + std::vector files; + + for (auto& adapter : adapters_) + { + for (const FileInfo& file : adapter->listFiles(folder)) + { + auto it = std::find_if(files.begin(), files.end(), [&](const FileInfo& existing) + { + return existing.path == file.path; + }); + if (it != files.end()) { + files.push_back(file); + } + } + } + + return files; +} + +FileInfo StackedFileSystemAdapter::getFileInfo(const fs::path& file) +{ + for (auto& adapter : adapters_) + { + FileInfo fileInfo = adapter->getFileInfo(file); + if (fileInfo.exists) { + return fileInfo; + } + } + + return {}; +} + +StreamError StackedFileSystemAdapter::open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) +{ + for (auto& adapter : adapters_) + { + FileInfo fileInfo = adapter->getFileInfo(path); + if (fileInfo.exists) { + return adapter->open(path, mode, outStream); + } + } + return StreamError::IO_ERROR; +} + +} // namespace mijin diff --git a/source/mijin/virtual_filesystem/stacked.hpp b/source/mijin/virtual_filesystem/stacked.hpp new file mode 100644 index 0000000..cdc6924 --- /dev/null +++ b/source/mijin/virtual_filesystem/stacked.hpp @@ -0,0 +1,52 @@ + +#pragma once + +#if !defined(MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED) +#define MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED 1 + +#include +#include +#include "./filesystem.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +class StackedFileSystemAdapter : public FileSystemAdapter +{ +private: + std::vector> adapters_; +public: + std::vector getRoots() override; + fs::path getHomeFolder() override; + std::vector listFiles(const fs::path& folder) override; + FileInfo getFileInfo(const fs::path& file) override; + StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr& outStream) override; + + inline void addAdapter(std::unique_ptr&& adapter) { + adapters_.push_back(std::move(adapter)); + } + template + inline void emplaceAdapter(TArgs&&... args) { + addAdapter(std::make_unique(std::forward(args)...)); + } +}; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED)