intial commit
This commit is contained in:
commit
da781b87f2
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@ -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
|
24
source/mijin/SConscript
Normal file
24
source/mijin/SConscript
Normal file
@ -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')
|
213
source/mijin/async/coroutine.cpp
Normal file
213
source/mijin/async/coroutine.cpp
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
|
||||||
|
#include "./coroutine.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
#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<StoredTask> task = queuedTasks_.tryPop();
|
||||||
|
if (!task.has_value()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to directly move it into the next queue
|
||||||
|
if (readyTasks_.tryPushMaybeMove(*task)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise park it
|
||||||
|
parkedTasks_.push_back(std::move(*task));
|
||||||
|
}
|
||||||
|
|
||||||
|
// next collect tasks returning from the worker threads
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::optional<StoredTask> task = returningTasks_.tryPop();
|
||||||
|
if (!task.has_value()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task->task == nullptr || task->task->status() == TaskStatus::FINISHED) {
|
||||||
|
continue; // task has been transferred or finished
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task->task->canResume() && readyTasks_.tryPushMaybeMove(*task)) {
|
||||||
|
continue; // instantly resume, no questions asked
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise park it for future processing
|
||||||
|
parkedTasks_.push_back(std::move(*task));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiThreadedTaskLoop::workerThread(std::stop_token stopToken, std::size_t workerId) // NOLINT(performance-unnecessary-value-param)
|
||||||
|
{
|
||||||
|
currentLoopStorage() = this; // forever (on this thread)
|
||||||
|
|
||||||
|
std::array<char, 16> threadName;
|
||||||
|
(void) std::snprintf(threadName.data(), 16, "Task Worker %lu", static_cast<unsigned long>(workerId));
|
||||||
|
setCurrentThreadName(threadName.data());
|
||||||
|
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
// try to fetch a task to run
|
||||||
|
std::optional<StoredTask> task = readyTasks_.tryPop();
|
||||||
|
if (!task.has_value())
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// run it
|
||||||
|
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
|
648
source/mijin/async/coroutine.hpp
Normal file
648
source/mijin/async/coroutine.hpp
Normal file
@ -0,0 +1,648 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef MIJIN_ASYNC_COROUTINE_HPP_INCLUDED
|
||||||
|
#define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <coroutine>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#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<typename T>
|
||||||
|
struct TaskState;
|
||||||
|
|
||||||
|
class TaskLoop;
|
||||||
|
|
||||||
|
template<typename TResult = void>
|
||||||
|
class TaskBase;
|
||||||
|
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
template<typename TReturn, typename TPromise>
|
||||||
|
struct TaskReturn
|
||||||
|
{
|
||||||
|
template<typename... TArgs>
|
||||||
|
constexpr void return_value(TArgs&&... args) noexcept {
|
||||||
|
*(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::forward<TArgs>(args)...), TaskStatus::FINISHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void return_value(TReturn value) noexcept {
|
||||||
|
*(static_cast<TPromise&>(*this).state_) = TaskState<TReturn>(TReturn(std::move(value)), TaskStatus::FINISHED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TPromise>
|
||||||
|
struct TaskReturn<void, TPromise>
|
||||||
|
{
|
||||||
|
constexpr void return_void() noexcept {
|
||||||
|
static_cast<TPromise&>(*this).state_->status = TaskStatus::FINISHED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct TaskState
|
||||||
|
{
|
||||||
|
Optional<T> 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<void>
|
||||||
|
{
|
||||||
|
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<typename TValue>
|
||||||
|
struct TaskAwaitableFuture
|
||||||
|
{
|
||||||
|
FuturePtr<TValue> 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<TValue, void>) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return std::move(future->get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... TArgs>
|
||||||
|
struct TaskAwaitableSignal
|
||||||
|
{
|
||||||
|
std::shared_ptr<std::tuple<TArgs...>> 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<typename TSingleArg>
|
||||||
|
struct TaskAwaitableSignal<TSingleArg>
|
||||||
|
{
|
||||||
|
std::shared_ptr<TSingleArg> 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<typename TTraits>
|
||||||
|
struct TaskPromise : impl::TaskReturn<typename TTraits::result_t, TaskPromise<TTraits>>
|
||||||
|
{
|
||||||
|
using handle_t = std::coroutine_handle<TaskPromise>;
|
||||||
|
using task_t = typename TTraits::task_t;
|
||||||
|
using result_t = typename TTraits::result_t;
|
||||||
|
|
||||||
|
std::shared_ptr<TaskState<result_t>> state_ = std::make_shared<TaskState<result_t>>();
|
||||||
|
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<typename TValue>
|
||||||
|
// constexpr std::suspend_always yield_value(TValue value) noexcept {
|
||||||
|
// *state_ = TaskState<result_t>(std::move(value), TaskStatus::YIELDED);
|
||||||
|
// return {};
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: implement yielding (can't use futures for this)
|
||||||
|
|
||||||
|
constexpr void unhandled_exception() noexcept {}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
auto await_transform(FuturePtr<TValue> future) noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!");
|
||||||
|
TaskAwaitableFuture<TValue> 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<typename TResultOther>
|
||||||
|
auto await_transform(TaskBase<TResultOther> task) noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(loop_ != nullptr, "Cannot await another task outside of a loop!");
|
||||||
|
auto future = delayEvaluation<TResultOther>(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<typename TFirstArg, typename TSecondArg, typename... TArgs>
|
||||||
|
auto await_transform(Signal<TFirstArg, TSecondArg, TArgs...>& signal) noexcept
|
||||||
|
{
|
||||||
|
auto data = std::make_shared<std::tuple<TFirstArg, TSecondArg, TArgs...>>();
|
||||||
|
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<TFirstArg, TSecondArg, TArgs...> awaitable{data};
|
||||||
|
state_->status = TaskStatus::WAITING;
|
||||||
|
return awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TFirstArg>
|
||||||
|
auto await_transform(Signal<TFirstArg>& signal) noexcept
|
||||||
|
{
|
||||||
|
auto data = std::make_shared<TFirstArg>();
|
||||||
|
signal.connect([this, data](TFirstArg arg0) mutable
|
||||||
|
{
|
||||||
|
*data = std::move(arg0);
|
||||||
|
state_->status = TaskStatus::SUSPENDED;
|
||||||
|
}, Oneshot::YES);
|
||||||
|
TaskAwaitableSignal<TFirstArg> 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<typename TResult>
|
||||||
|
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<Traits>;
|
||||||
|
using handle_t = typename promise_type::handle_t;
|
||||||
|
private:
|
||||||
|
handle_t handle_;
|
||||||
|
std::shared_ptr<TaskState<result_t>> 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<TResult>& resume() noexcept
|
||||||
|
{
|
||||||
|
state_->status = TaskStatus::RUNNING;
|
||||||
|
handle_.resume();
|
||||||
|
return *state_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr TaskState<TResult>& 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<typename TTask>
|
||||||
|
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<typename TTask>
|
||||||
|
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<typename TTask::result_t, void>) {
|
||||||
|
// 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<typename TTask>
|
||||||
|
std::unique_ptr<WrappedTask<TTask>> wrapTask(TTask&& task) noexcept
|
||||||
|
{
|
||||||
|
return std::make_unique<WrappedTask<TTask>>(std::forward<TTask>(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<wrapped_task_t>;
|
||||||
|
|
||||||
|
struct StoredTask
|
||||||
|
{
|
||||||
|
wrapped_task_base_ptr_t task;
|
||||||
|
std::function<void(StoredTask&)> setFuture;
|
||||||
|
std::any resultData;
|
||||||
|
};
|
||||||
|
using task_vector_t = std::vector<StoredTask>;
|
||||||
|
|
||||||
|
template<typename TTask>
|
||||||
|
using wrapped_task_ptr_t = std::unique_ptr<WrappedTask<TTask>>;
|
||||||
|
public:
|
||||||
|
TaskLoop() = default;
|
||||||
|
TaskLoop(const TaskLoop&) = delete;
|
||||||
|
TaskLoop(TaskLoop&&) = delete;
|
||||||
|
virtual ~TaskLoop() = default;
|
||||||
|
|
||||||
|
TaskLoop& operator=(const TaskLoop&) = delete;
|
||||||
|
TaskLoop& operator=(TaskLoop&&) = delete;
|
||||||
|
|
||||||
|
template<typename TResult>
|
||||||
|
inline FuturePtr<TResult> addTask(TaskBase<TResult> 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<typename TResult>
|
||||||
|
static inline void setFutureHelper(StoredTask& storedTask) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TResult = void>
|
||||||
|
using Task = TaskBase<TResult>;
|
||||||
|
|
||||||
|
class SimpleTaskLoop : public TaskLoop
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
task_vector_t tasks_;
|
||||||
|
task_vector_t newTasks_;
|
||||||
|
task_vector_t::iterator currentTask_;
|
||||||
|
MessageQueue<StoredTask> 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<StoredTask> queuedTasks_; // tasks that should be appended to parked tasks
|
||||||
|
MessageQueue<StoredTask> readyTasks_; // task queue to send tasks to a worker thread
|
||||||
|
MessageQueue<StoredTask> returningTasks_; // task that have executed on a worker thread and return for further processing
|
||||||
|
std::jthread managerThread_;
|
||||||
|
std::vector<std::jthread> 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<typename TResult>
|
||||||
|
TaskBase<TResult>::~TaskBase() noexcept
|
||||||
|
{
|
||||||
|
if (handle_)
|
||||||
|
{
|
||||||
|
// handle_.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TResult>
|
||||||
|
inline FuturePtr<TResult> TaskLoop::addTask(TaskBase<TResult> task) noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!");
|
||||||
|
task.setLoop(this);
|
||||||
|
|
||||||
|
auto future = std::make_shared<Future<TResult>>();
|
||||||
|
|
||||||
|
auto setFuture = &setFutureHelper<TResult>;
|
||||||
|
|
||||||
|
// 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<typename TResult>
|
||||||
|
/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) noexcept
|
||||||
|
{
|
||||||
|
TaskBase<TResult>& task = *static_cast<TaskBase<TResult>*>(storedTask.task->raw());
|
||||||
|
auto future = std::any_cast<FuturePtr<TResult>>(storedTask.resultData);
|
||||||
|
|
||||||
|
if constexpr (!std::is_same_v<TResult, void>)
|
||||||
|
{
|
||||||
|
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<StoredTask> 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<template<typename...> typename TCollection, typename TType, typename... TTemplateArgs>
|
||||||
|
Task<> c_allDone(const TCollection<FuturePtr<TType>, TTemplateArgs...>& futures)
|
||||||
|
{
|
||||||
|
bool allDone = true;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
allDone = true;
|
||||||
|
for (const FuturePtr<TType>& 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
|
140
source/mijin/async/future.hpp
Normal file
140
source/mijin/async/future.hpp
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
|
||||||
|
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include "./signal.hpp"
|
||||||
|
#include "../debug/assert.hpp"
|
||||||
|
#include "../container/optional.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
template<typename TValue>
|
||||||
|
class Future;
|
||||||
|
|
||||||
|
// TODO: add support for mutexes and waiting for futures
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
template<typename TValue>
|
||||||
|
struct FutureStorage
|
||||||
|
{
|
||||||
|
std::optional<TValue> value;
|
||||||
|
|
||||||
|
void setValue(TValue value_) noexcept { value = std::move(value_); }
|
||||||
|
[[nodiscard]] TValue& getValue() noexcept { return value.value(); }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
struct FutureStorage<TValue&>
|
||||||
|
{
|
||||||
|
std::optional<TValue*> value;
|
||||||
|
|
||||||
|
void setValue(TValue& value_) noexcept { value = &value_; }
|
||||||
|
[[nodiscard]] TValue& getValue() const noexcept { return *value.value(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct FutureStorage<void>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
class Future
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
impl::FutureStorage<TValue> 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<TValue, void>) {
|
||||||
|
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<TValue, void>) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return value_.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr bool ready() const noexcept
|
||||||
|
{
|
||||||
|
return isSet_;
|
||||||
|
}
|
||||||
|
public: // modification
|
||||||
|
template<typename TArg> requires (!std::is_same_v<TValue, void>)
|
||||||
|
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<TValue, void>) {
|
||||||
|
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<typename TValue>
|
||||||
|
using FuturePtr = std::shared_ptr<Future<TValue>>;
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
|
132
source/mijin/async/message_queue.hpp
Normal file
132
source/mijin/async/message_queue.hpp
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_MESSAGE_QUEUE_HPP_INCLUDED)
|
||||||
|
#define MIJIN_MESSAGE_QUEUE_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
#include "../util/bitarray.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TMessage, std::size_t bufferSize = 32>
|
||||||
|
class MessageQueue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::array<TMessage, bufferSize> messages;
|
||||||
|
mijin::BitArray<bufferSize, true> messageReady;
|
||||||
|
std::atomic_uint writePos = 0;
|
||||||
|
std::atomic_uint readPos = 0;
|
||||||
|
public:
|
||||||
|
MessageQueue() = default;
|
||||||
|
MessageQueue(const MessageQueue&) = delete;
|
||||||
|
MessageQueue(MessageQueue&&) 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<TMessage> tryPop();
|
||||||
|
[[nodiscard]] TMessage wait();
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TRequest, typename TResponse, std::size_t requestBufferSize = 32, std::size_t responseBufferSize = 32>
|
||||||
|
struct TaskMessageQueue
|
||||||
|
{
|
||||||
|
MessageQueue<TRequest, requestBufferSize> requests;
|
||||||
|
MessageQueue<TResponse, responseBufferSize> responses;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TMessage, std::size_t bufferSize>
|
||||||
|
bool MessageQueue<TMessage, bufferSize>::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<typename TMessage, std::size_t bufferSize>
|
||||||
|
void MessageQueue<TMessage, bufferSize>::push(TMessage message)
|
||||||
|
{
|
||||||
|
while (!tryPushMaybeMove(message)) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TMessage, std::size_t bufferSize>
|
||||||
|
std::optional<TMessage> MessageQueue<TMessage, bufferSize>::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<typename TMessage, std::size_t bufferSize>
|
||||||
|
TMessage MessageQueue<TMessage, bufferSize>::wait()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::optional<TMessage> message = tryPop();
|
||||||
|
if (message.has_value()) {
|
||||||
|
return message.value();
|
||||||
|
}
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_MESSAGE_QUEUE_HPP_INCLUDED)
|
126
source/mijin/async/signal.hpp
Normal file
126
source/mijin/async/signal.hpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)
|
||||||
|
#define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include "../util/flag.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
MIJIN_DEFINE_FLAG(Oneshot);
|
||||||
|
|
||||||
|
template<typename... TArgs>
|
||||||
|
class Signal
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using handler_t = std::function<void(TArgs...)>;
|
||||||
|
using token_t = std::uint32_t;
|
||||||
|
private:
|
||||||
|
struct RegisteredHandler
|
||||||
|
{
|
||||||
|
handler_t callable;
|
||||||
|
std::weak_ptr<void> referenced;
|
||||||
|
token_t token;
|
||||||
|
Oneshot oneshot = Oneshot::NO;
|
||||||
|
};
|
||||||
|
using handler_vector_t = std::vector<RegisteredHandler>;
|
||||||
|
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<typename THandler, typename TWeak = void>
|
||||||
|
inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) noexcept;
|
||||||
|
inline void disconnect(token_t token) noexcept;
|
||||||
|
inline void emit(TArgs&&... args) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename... TArgs>
|
||||||
|
template<typename THandler, typename TWeak>
|
||||||
|
inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> 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<typename... TArgs>
|
||||||
|
inline void Signal<TArgs...>::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<typename... TArgs>
|
||||||
|
inline void Signal<TArgs...>::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<TArgs>(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)
|
237
source/mijin/container/boxed_object.hpp
Normal file
237
source/mijin/container/boxed_object.hpp
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED)
|
||||||
|
#define MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#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<typename T>
|
||||||
|
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<typename... TArgs>
|
||||||
|
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<TArgs>(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<typename T>
|
||||||
|
class BoxedObjectIterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = T;
|
||||||
|
private:
|
||||||
|
BoxedObject<T>* box_ = nullptr;
|
||||||
|
#if MIJIN_CHECKED_ITERATORS
|
||||||
|
BoxedObject<T>* start_ = nullptr;
|
||||||
|
BoxedObject<T>* end_ = nullptr;
|
||||||
|
#endif
|
||||||
|
public:
|
||||||
|
BoxedObjectIterator() = default;
|
||||||
|
explicit constexpr BoxedObjectIterator(BoxedObject<T>* box, BoxedObject<T>* start, BoxedObject<T>* 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<typename T>
|
||||||
|
inline BoxedObjectIterator<T> operator+(std::iter_difference_t<T> diff, const BoxedObjectIterator<T>& iter) {
|
||||||
|
return iter + diff;
|
||||||
|
}
|
||||||
|
static_assert(std::random_access_iterator<BoxedObjectIterator<int>>);
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
BoxedObjectIterator<T>& BoxedObjectIterator<T>::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<typename T>
|
||||||
|
T& BoxedObjectIterator<T>::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<typename T>
|
||||||
|
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator++()
|
||||||
|
{
|
||||||
|
#if MIJIN_CHECKED_ITERATORS
|
||||||
|
MIJIN_ASSERT(box_ < end_, "BoxedObjectIterator::operator++(): Attempt to iterator past the end.");
|
||||||
|
#endif
|
||||||
|
++box_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
BoxedObjectIterator<T> BoxedObjectIterator<T>::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<typename T>
|
||||||
|
BoxedObjectIterator<T>& BoxedObjectIterator<T>::operator--()
|
||||||
|
{
|
||||||
|
#if MIJIN_CHECKED_ITERATORS
|
||||||
|
MIJIN_ASSERT(box_ > start_, "BoxedObjectIterator::operator--(): Attempt to iterator past start.");
|
||||||
|
#endif
|
||||||
|
--box_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
BoxedObjectIterator<T> BoxedObjectIterator<T>::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<typename T>
|
||||||
|
BoxedObjectIterator<T> BoxedObjectIterator<T>::operator+(difference_type diff) const
|
||||||
|
{
|
||||||
|
BoxedObjectIterator copy(*this);
|
||||||
|
copy += diff;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_CONTAINER_BOXED_OBJECT_HPP_INCLUDED)
|
145
source/mijin/container/cached_array.hpp
Normal file
145
source/mijin/container/cached_array.hpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED)
|
||||||
|
#define MIJIN_CONTAINER_CACHED_ARRAY_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
#include "../debug/assert.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename T, std::size_t cacheSize = 32>
|
||||||
|
class CachedArray
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using element_type = T;
|
||||||
|
using value_type = std::remove_cv_t<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<size_type>::max();
|
||||||
|
private:
|
||||||
|
struct CacheEntry
|
||||||
|
{
|
||||||
|
size_type index = INVALID_INDEX;
|
||||||
|
std::size_t useIndex = 0;
|
||||||
|
std::optional<T> value;
|
||||||
|
};
|
||||||
|
mutable std::array<CacheEntry, cacheSize> cache_;
|
||||||
|
std::vector<std::optional<T>> 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<typename TSelf>
|
||||||
|
static auto atImpl(TSelf&& self, std::size_t index);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename T, std::size_t cacheSize>
|
||||||
|
auto CachedArray<T, cacheSize>::at(std::size_t index) -> reference
|
||||||
|
{
|
||||||
|
return atImpl(*this, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, std::size_t cacheSize>
|
||||||
|
auto CachedArray<T, cacheSize>::at(std::size_t index) const -> const_reference
|
||||||
|
{
|
||||||
|
return atImpl(*this, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, std::size_t cacheSize>
|
||||||
|
void CachedArray<T, cacheSize>::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<typename T, std::size_t cacheSize>
|
||||||
|
template<typename TSelf>
|
||||||
|
auto CachedArray<T, cacheSize>::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)
|
133
source/mijin/container/inline_array.hpp
Normal file
133
source/mijin/container/inline_array.hpp
Normal file
@ -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<typename T, std::size_t maxSize>
|
||||||
|
class InlineArray
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using element_type = T;
|
||||||
|
using value_type = std::remove_cv_t<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<T>;
|
||||||
|
using const_iterator = BoxedObjectIterator<const T>;
|
||||||
|
private:
|
||||||
|
BoxedObject<T> 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<T>(&elements_[0], &elements_[0], &elements_[size_]); }
|
||||||
|
[[nodiscard]] const_iterator begin() const { return BoxedObjectIterator<const T>(&elements_[0], &elements_[0], &elements_[size_]); }
|
||||||
|
[[nodiscard]] const_iterator cbegin() const { return BoxedObjectIterator<const T>(&elements_[0], &elements_[0], &elements_[size_]); }
|
||||||
|
[[nodiscard]] iterator end() { return BoxedObjectIterator<T>(&elements_[size_], &elements_[0], &elements_[size_]); }
|
||||||
|
[[nodiscard]] const_iterator end() const { return BoxedObjectIterator<const T>(&elements_[size_], &elements_[0], &elements_[size_]); }
|
||||||
|
[[nodiscard]] const_iterator cend() const { return BoxedObjectIterator<const T>(&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<typename T, std::size_t numElements>
|
||||||
|
InlineArray<T, numElements>::InlineArray(const InlineArray& other) : size_(other.size_)
|
||||||
|
{
|
||||||
|
for (size_type idx = 0; idx < size_; ++idx) {
|
||||||
|
other.elements_[idx].copyTo(elements_[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, std::size_t numElements>
|
||||||
|
InlineArray<T, numElements>::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<typename T, std::size_t numElements>
|
||||||
|
InlineArray<T, numElements>::~InlineArray()
|
||||||
|
{
|
||||||
|
for (std::size_t idx = 0; idx < size_; ++idx) {
|
||||||
|
elements_[idx].destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, std::size_t numElements>
|
||||||
|
auto InlineArray<T, numElements>::operator[](size_type index) -> reference
|
||||||
|
{
|
||||||
|
return at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, std::size_t numElements>
|
||||||
|
auto InlineArray<T, numElements>::operator[](size_type index) const -> const_reference
|
||||||
|
{
|
||||||
|
return at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, std::size_t numElements>
|
||||||
|
void InlineArray<T, numElements>::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)
|
90
source/mijin/container/map_view.hpp
Normal file
90
source/mijin/container/map_view.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED)
|
||||||
|
#define MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TKey, typename TValue, typename TMap = std::unordered_map<TKey, TValue>>
|
||||||
|
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 TKey, typename TValue, template<typename, typename, typename...> typename TMap>
|
||||||
|
// MapView(TMap<TKey, TValue>& map) -> MapView<TKey, TValue, TMap<TKey, TValue>>;
|
||||||
|
|
||||||
|
template<typename TMap>
|
||||||
|
MapView(TMap& map) -> MapView<typename TMap::key_type, typename TMap::mapped_type, TMap>;
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_CONTAINER_MAP_VIEW_HPP_INCLUDED)
|
298
source/mijin/container/optional.hpp
Normal file
298
source/mijin/container/optional.hpp
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)
|
||||||
|
#define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
#include "../debug/assert.hpp"
|
||||||
|
#include "../util/concepts.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
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<typename... TArgs>
|
||||||
|
constexpr void emplace(TArgs&&... args) noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(!used, "Attempting to emplace in an already set OptionalStorage!");
|
||||||
|
used = 1;
|
||||||
|
::new (data) T(std::forward<TArgs>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(used, "Attempting to clear an empty OptionalStorage!");
|
||||||
|
get().~T();
|
||||||
|
used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr T& get() noexcept { return *reinterpret_cast<T*>(data); }
|
||||||
|
[[nodiscard]] constexpr const T& get() const noexcept { return *reinterpret_cast<const T*>(data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<pointer_type T>
|
||||||
|
struct OptionalStorage<T>
|
||||||
|
{
|
||||||
|
static constexpr T invalidPointer() noexcept { return reinterpret_cast<T>(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<reference_type T>
|
||||||
|
struct OptionalStorage<T>
|
||||||
|
{
|
||||||
|
using pointer_t = std::remove_reference_t<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<typename TValue>
|
||||||
|
class Optional
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
impl::OptionalStorage<TValue> 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<typename TOther>
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr bool operator ==(const Optional<TOther>& other) const noexcept;
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]]
|
||||||
|
constexpr bool operator ==(const T& value) const noexcept;
|
||||||
|
template<typename T>
|
||||||
|
[[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<typename... Types>
|
||||||
|
void emplace(Types&&... params) noexcept;
|
||||||
|
public:
|
||||||
|
[[nodiscard]] inline std::remove_reference_t<TValue>& get() noexcept;
|
||||||
|
[[nodiscard]] inline const std::remove_reference_t<TValue>& get() const noexcept;
|
||||||
|
[[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); }
|
||||||
|
inline void reset() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
Optional<TValue>::Optional(const Optional& other) noexcept
|
||||||
|
{
|
||||||
|
if (other) {
|
||||||
|
emplace(other.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
Optional<TValue>::Optional(Optional&& other) noexcept
|
||||||
|
{
|
||||||
|
if (other)
|
||||||
|
{
|
||||||
|
emplace(std::move(other.get()));
|
||||||
|
other.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
Optional<TValue>::Optional(TValue value) noexcept
|
||||||
|
{
|
||||||
|
if constexpr (std::is_reference_v<TValue>) {
|
||||||
|
emplace(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emplace(std::move(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
Optional<TValue>::~Optional() noexcept
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
auto Optional<TValue>::operator =(const Optional& other) noexcept -> Optional&
|
||||||
|
{
|
||||||
|
if (&other == this) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
if (other) {
|
||||||
|
emplace(other.get());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
auto Optional<TValue>::operator =(Optional&& other) noexcept -> Optional&
|
||||||
|
{
|
||||||
|
if (&other == this) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
if (other)
|
||||||
|
{
|
||||||
|
emplace(std::move(other.get()));
|
||||||
|
other.reset();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
auto Optional<TValue>::operator =(TValue value) noexcept -> Optional&
|
||||||
|
{
|
||||||
|
if constexpr (std::is_reference_v<TValue>) {
|
||||||
|
emplace(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emplace(std::move(value));
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
auto Optional<TValue>::operator =(NullOptional) noexcept -> Optional&
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
template<typename TOther>
|
||||||
|
constexpr bool Optional<TValue>::operator ==(const Optional<TOther>& other) const noexcept
|
||||||
|
{
|
||||||
|
if (empty())
|
||||||
|
{
|
||||||
|
return other.empty();
|
||||||
|
}
|
||||||
|
if (!other.empty())
|
||||||
|
{
|
||||||
|
return get() == other.get();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
template<typename T>
|
||||||
|
constexpr bool Optional<TValue>::operator ==(const T& value) const noexcept
|
||||||
|
{
|
||||||
|
if (empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return get() == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
template<typename... Types>
|
||||||
|
void Optional<TValue>::emplace(Types&&... params) noexcept
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
storage_.emplace(std::forward<Types>(params)...);
|
||||||
|
MIJIN_ASSERT(!empty(), "Something is wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
inline std::remove_reference_t<TValue>& Optional<TValue>::get() noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!");
|
||||||
|
return storage_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
inline const std::remove_reference_t<TValue>& Optional<TValue>::get() const noexcept
|
||||||
|
{
|
||||||
|
MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!");
|
||||||
|
return storage_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
inline void Optional<TValue>::reset() noexcept
|
||||||
|
{
|
||||||
|
if (empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)
|
104
source/mijin/container/typeless_buffer.hpp
Normal file
104
source/mijin/container/typeless_buffer.hpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED)
|
||||||
|
#define MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
#include "../debug/assert.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class BufferView;
|
||||||
|
|
||||||
|
class TypelessBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using size_type = std::size_t;
|
||||||
|
private:
|
||||||
|
std::vector<std::byte> 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<typename T>
|
||||||
|
[[nodiscard]] BufferView<T> makeBufferView() { return BufferView<T>(this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class BufferView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using element_type = T;
|
||||||
|
using value_type = std::remove_cv_t<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<T*>(buffer_->data()) : nullptr; }
|
||||||
|
[[nodiscard]] inline const_iterator begin() const { return buffer_ ? static_cast<T*>(buffer_->data()) : nullptr; }
|
||||||
|
[[nodiscard]] inline const_iterator cbegin() const { return begin(); }
|
||||||
|
[[nodiscard]] inline iterator end() { return buffer_ ? static_cast<T*>(buffer_->data()) + size() : nullptr; }
|
||||||
|
[[nodiscard]] inline const_iterator end() const { return buffer_ ? static_cast<T*>(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)
|
146
source/mijin/debug/assert.hpp
Normal file
146
source/mijin/debug/assert.hpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_DEBUG_ASSERT_HPP_INCLUDED)
|
||||||
|
#define MIJIN_DEBUG_ASSERT_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
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 <signal.h>
|
||||||
|
#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<bool>(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<bool>(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)
|
87
source/mijin/debug/symbol_info.cpp
Normal file
87
source/mijin/debug/symbol_info.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
#include "symbol_info.hpp"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "../detect.hpp"
|
||||||
|
|
||||||
|
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||||
|
#if !defined(_GNU_SOURCE)
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#endif
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC
|
||||||
|
#include <cxxabi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal types
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal variables
|
||||||
|
//
|
||||||
|
|
||||||
|
static thread_local std::unordered_map<const void*, std::string> 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 = "<unknown>";
|
||||||
|
#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
|
33
source/mijin/debug/symbol_info.hpp
Normal file
33
source/mijin/debug/symbol_info.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED)
|
||||||
|
#define MIJIN_DEBUG_SYMBOL_INFO_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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)
|
78
source/mijin/detect.hpp
Normal file
78
source/mijin/detect.hpp
Normal file
@ -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(<features.h>)
|
||||||
|
#include <features.h>
|
||||||
|
#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)
|
308
source/mijin/io/stream.cpp
Normal file
308
source/mijin/io/stream.cpp
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
|
||||||
|
#include "./stream.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
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<std::uint32_t>::max());
|
||||||
|
const std::uint32_t length = static_cast<std::uint32_t>(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<std::uint8_t> 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<const std::uint8_t> 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<std::size_t>(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<long>(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<std::uint8_t> data)
|
||||||
|
{
|
||||||
|
assert(!isOpen());
|
||||||
|
data_ = data;
|
||||||
|
pos_ = 0;
|
||||||
|
canWrite_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryStream::openRO(std::span<const std::uint8_t> data)
|
||||||
|
{
|
||||||
|
assert(!isOpen());
|
||||||
|
data_ = std::span<std::uint8_t>(const_cast<std::uint8_t*>(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<std::uint8_t> 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<long>(pos_), numBytes, buffer.begin());
|
||||||
|
if (outBytesRead) {
|
||||||
|
*outBytesRead = numBytes;
|
||||||
|
}
|
||||||
|
pos_ += numBytes;
|
||||||
|
return StreamError::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||||
|
{
|
||||||
|
assert(isOpen());
|
||||||
|
assert(canWrite_);
|
||||||
|
|
||||||
|
if (availableBytes() < buffer.size()) {
|
||||||
|
return StreamError::IO_ERROR;
|
||||||
|
}
|
||||||
|
std::copy(buffer.begin(), buffer.end(), data_.begin() + static_cast<long>(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<std::intptr_t>(pos_) + pos;
|
||||||
|
break;
|
||||||
|
case SeekMode::RELATIVE_TO_END:
|
||||||
|
newPos = static_cast<std::intptr_t>(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
|
181
source/mijin/io/stream.hpp
Normal file
181
source/mijin/io/stream.hpp
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
|
||||||
|
#define MIJIN_IO_STREAM_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) = 0;
|
||||||
|
virtual StreamError writeRaw(std::span<const std::uint8_t> 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<std::uint8_t*>(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<const std::uint8_t*>(data);
|
||||||
|
return writeRaw(std::span(ptr, ptr + bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline StreamError read(T& value)
|
||||||
|
{
|
||||||
|
return readRaw(&value, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline StreamError readSpan(T& values)
|
||||||
|
{
|
||||||
|
auto asSpan = std::span(values);
|
||||||
|
return readRaw(asSpan.data(), asSpan.size_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TItBegin, typename TItEnd>
|
||||||
|
inline StreamError readSpan(TItBegin&& begin, TItEnd&& end)
|
||||||
|
{
|
||||||
|
auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
|
||||||
|
return readRaw(asSpan.data(), asSpan.size_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline StreamError write(const T& value)
|
||||||
|
{
|
||||||
|
return writeRaw(&value, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline StreamError writeSpan(const T& values)
|
||||||
|
{
|
||||||
|
auto asSpan = std::span(values);
|
||||||
|
return writeRaw(asSpan.data(), asSpan.size_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TItBegin, typename TItEnd>
|
||||||
|
inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end)
|
||||||
|
{
|
||||||
|
return writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(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<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
|
||||||
|
StreamError writeRaw(std::span<const std::uint8_t> 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<std::uint8_t> data_;
|
||||||
|
std::size_t pos_ = 0;
|
||||||
|
bool canWrite_ = false;
|
||||||
|
public:
|
||||||
|
void openRW(std::span<std::uint8_t> data);
|
||||||
|
void openRO(std::span<const std::uint8_t> 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<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
|
||||||
|
StreamError writeRaw(std::span<const std::uint8_t> 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)
|
31
source/mijin/memory/data_pool.cpp
Normal file
31
source/mijin/memory/data_pool.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
#include "data_pool.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal types
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal variables
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal functions
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
90
source/mijin/memory/data_pool.hpp
Normal file
90
source/mijin/memory/data_pool.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED)
|
||||||
|
#define MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<std::size_t PAGE_SIZE = 4096ul * 1024ul>
|
||||||
|
struct DataPool
|
||||||
|
{
|
||||||
|
static_assert(PAGE_SIZE % alignof(std::max_align_t) == 0);
|
||||||
|
|
||||||
|
struct alignas(std::max_align_t) Page : std::array<std::uint8_t, PAGE_SIZE> {};
|
||||||
|
|
||||||
|
using page_ptr_t = std::unique_ptr<Page>;
|
||||||
|
|
||||||
|
std::vector<page_ptr_t> 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<typename T>
|
||||||
|
T* allocateAs(std::size_t elements = 1) {
|
||||||
|
return static_cast<T*>(allocate(sizeof(T) * elements, alignof(T)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<std::size_t PAGE_SIZE>
|
||||||
|
void* DataPool<PAGE_SIZE>::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<Page>());
|
||||||
|
}
|
||||||
|
offset = PAGE_SIZE * (pages.size() - 1);
|
||||||
|
}
|
||||||
|
std::uint8_t* result = &(*pages[page])[localOffset];
|
||||||
|
offset += bytes;
|
||||||
|
assert(reinterpret_cast<std::uintptr_t>(result) % alignment == 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_MEMORY_DATA_POOL_HPP_INCLUDED)
|
128
source/mijin/types/name.cpp
Normal file
128
source/mijin/types/name.cpp
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
#include "./name.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// 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<std::string>& getGlobalNames()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> names;
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string>& getLocalNames()
|
||||||
|
{
|
||||||
|
static thread_local std::vector<std::string> names;
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::shared_mutex& getGlobalNamesMutex()
|
||||||
|
{
|
||||||
|
static std::shared_mutex mutex;
|
||||||
|
return mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<std::string_view, std::size_t>& getLocalNameCache()
|
||||||
|
{
|
||||||
|
static std::unordered_map<std::string_view, std::size_t> cache;
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copyNamesToLocal()
|
||||||
|
{
|
||||||
|
const std::size_t oldSize = getLocalNames().size();
|
||||||
|
getLocalNames().resize(getGlobalNames().size());
|
||||||
|
std::copy(getGlobalNames().begin() + static_cast<long>(oldSize), getGlobalNames().end(), getLocalNames().begin() + static_cast<long>(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<std::size_t>::max()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return getLocalNames().at(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
63
source/mijin/types/name.hpp
Normal file
63
source/mijin/types/name.hpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_TYPES_NAME_HPP_INCLUDED)
|
||||||
|
#define MIJIN_TYPES_NAME_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
class Name
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::size_t id_ = std::numeric_limits<std::size_t>::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<mijin::Name> : hash<std::size_t>
|
||||||
|
{
|
||||||
|
std::size_t operator()(mijin::Name name) const noexcept {
|
||||||
|
return hash<std::size_t>::operator()(name.getID());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_TYPES_NAME_HPP_INCLUDED)
|
75
source/mijin/util/bitarray.hpp
Normal file
75
source/mijin/util/bitarray.hpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_BITARRAY_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_BITARRAY_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
template<std::size_t numBits, bool threadSafe = false>
|
||||||
|
struct BitArray
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using byte_type = std::conditional_t<threadSafe, std::atomic_uint8_t, std::uint8_t>;
|
||||||
|
std::array<byte_type, (numBits + 7) / 8> 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)
|
92
source/mijin/util/bitflags.hpp
Normal file
92
source/mijin/util/bitflags.hpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_BITFLAGS_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TBits>
|
||||||
|
struct BitFlags
|
||||||
|
{
|
||||||
|
constexpr TBits& operator |=(const BitFlags& other) {
|
||||||
|
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
|
||||||
|
*(std::bit_cast<std::byte*>(asBits()) + idx) |= *(std::bit_cast<const std::byte*>(other.asBits()) + idx);
|
||||||
|
}
|
||||||
|
return *asBits();
|
||||||
|
}
|
||||||
|
constexpr TBits& operator &=(const BitFlags& other) {
|
||||||
|
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
|
||||||
|
*(std::bit_cast<std::byte*>(asBits()) + idx) &= *(std::bit_cast<const std::byte*>(other.asBits()) + idx);
|
||||||
|
}
|
||||||
|
return *asBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr TBits& operator ^=(const BitFlags& other) {
|
||||||
|
for (std::size_t idx = 0; idx < sizeof(TBits); ++idx) {
|
||||||
|
*(std::bit_cast<std::byte*>(asBits()) + idx) ^= *(std::bit_cast<const std::byte*>(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<const std::byte*>(asBits()) + idx) != std::byte(0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!() const {
|
||||||
|
return !static_cast<bool>(*this);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
constexpr TBits* asBits() { return static_cast<TBits*>(this); }
|
||||||
|
constexpr const TBits* asBits() const { return static_cast<const TBits*>(this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_BITFLAGS_HPP_INCLUDED)
|
48
source/mijin/util/concepts.hpp
Normal file
48
source/mijin/util/concepts.hpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_CONCEPTS_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept standard_type = std::is_standard_layout_v<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept trivial_type = std::is_trivial_v<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept enum_type = std::is_enum_v<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept arithmetic_type = std::is_arithmetic_v<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept pointer_type = std::is_pointer_v<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept reference_type = std::is_reference_v<T>;
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_CONCEPTS_HPP_INCLUDED)
|
161
source/mijin/util/flag.hpp
Normal file
161
source/mijin/util/flag.hpp
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_FLAG_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_FLAG_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#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<typename T>
|
||||||
|
concept FlagType = requires (T& value)
|
||||||
|
{
|
||||||
|
value = T::YES;
|
||||||
|
value = T::NO;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
template<std::size_t offset, FlagType... TFlags>
|
||||||
|
class FlagSetStorage;
|
||||||
|
|
||||||
|
template<std::size_t offset, FlagType TFirst, FlagType... TMore>
|
||||||
|
class FlagSetStorage<offset, TFirst, TMore...> : public FlagSetStorage<offset + 1, TMore...>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using base_t = FlagSetStorage<offset + 1, TMore...>;
|
||||||
|
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<std::size_t offset>
|
||||||
|
class FlagSetStorage<offset>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
using data_t = uint_next_t<offset>;
|
||||||
|
protected:
|
||||||
|
data_t data_ = data_t(0);
|
||||||
|
};
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
template<FlagType... TFlags>
|
||||||
|
class FlagSet
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using storage_t = impl::FlagSetStorage<0, TFlags...>;
|
||||||
|
private:
|
||||||
|
storage_t storage_;
|
||||||
|
public:
|
||||||
|
FlagSet() = default;
|
||||||
|
FlagSet(const FlagSet&) = default;
|
||||||
|
template<FlagType... TFlags2>
|
||||||
|
constexpr FlagSet(TFlags2... flags)
|
||||||
|
{
|
||||||
|
(set(flags), ...);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
FlagSet& operator=(const FlagSet&) = default;
|
||||||
|
template<FlagType T> requires is_any_type_v<T, TFlags...>
|
||||||
|
FlagSet& operator=(T flag) noexcept
|
||||||
|
{
|
||||||
|
reset(flag);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<FlagType T> requires is_any_type_v<T, TFlags...>
|
||||||
|
FlagSet& operator|=(T flag) noexcept
|
||||||
|
{
|
||||||
|
set(flag);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<FlagType T> requires is_any_type_v<T, TFlags...>
|
||||||
|
FlagSet& operator&=(T flag) noexcept
|
||||||
|
{
|
||||||
|
unset(flag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_FLAG_HPP_INCLUDED)
|
47
source/mijin/util/os.cpp
Normal file
47
source/mijin/util/os.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
#include "os.hpp"
|
||||||
|
|
||||||
|
#include "../detect.hpp"
|
||||||
|
|
||||||
|
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||||
|
#include <pthread.h>
|
||||||
|
#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
|
30
source/mijin/util/os.hpp
Normal file
30
source/mijin/util/os.hpp
Normal file
@ -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)
|
52
source/mijin/util/string.hpp
Normal file
52
source/mijin/util/string.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template <typename TRange, typename TValue = typename TRange::value_type>
|
||||||
|
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<TValue>(oss, delimiter));
|
||||||
|
first = prev(last);
|
||||||
|
}
|
||||||
|
if (first != last)
|
||||||
|
{
|
||||||
|
oss << *first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
126
source/mijin/util/template_string.hpp
Normal file
126
source/mijin/util/template_string.hpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#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<char... chars>
|
||||||
|
using template_string = std::integer_sequence<char, chars...>;
|
||||||
|
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
struct TemplateStringHelper;
|
||||||
|
|
||||||
|
template<char... chars>
|
||||||
|
struct TemplateStringHelper<template_string<chars...>>
|
||||||
|
{
|
||||||
|
static constexpr const char value[sizeof...(chars) + 1] = {chars..., '\0'}; // NOLINT
|
||||||
|
};
|
||||||
|
|
||||||
|
template<char chr, char... chars>
|
||||||
|
constexpr template_string<chr, chars...> prependToString(template_string<chars...>)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<char... chars>
|
||||||
|
struct TemplateStringBuilder {};
|
||||||
|
|
||||||
|
template<char firstChar, char... chars>
|
||||||
|
struct TemplateStringBuilder<firstChar, chars...>
|
||||||
|
{
|
||||||
|
using previous_seq_t = typename TemplateStringBuilder<chars...>::seq_t;
|
||||||
|
using seq_t = decltype(prependToString<firstChar>(previous_seq_t()));
|
||||||
|
};
|
||||||
|
|
||||||
|
template<char... chars>
|
||||||
|
struct TemplateStringBuilder<'\0', chars...>
|
||||||
|
{
|
||||||
|
using seq_t = template_string<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<std::size_t pos, std::size_t len>
|
||||||
|
constexpr char charAt(const char (&text)[len])
|
||||||
|
{
|
||||||
|
if constexpr (pos < len) {
|
||||||
|
return text[pos];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<char... chars>
|
||||||
|
typename TemplateStringBuilder<chars...>::seq_t buildTemplateString()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<char... chars>
|
||||||
|
using template_string_builder = typename TemplateStringBuilder<chars...>::seq_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline constexpr auto template_string_v = impl::TemplateStringHelper<T>::value;
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_TEMPLATE_STRING_HPP_INCLUDED)
|
118
source/mijin/util/traits.hpp
Normal file
118
source/mijin/util/traits.hpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_TRAITS_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct always_false
|
||||||
|
{
|
||||||
|
static constexpr bool value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline constexpr bool always_false_v = always_false<T>::value;
|
||||||
|
|
||||||
|
template<auto V>
|
||||||
|
struct always_false_val
|
||||||
|
{
|
||||||
|
static constexpr bool value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<auto V>
|
||||||
|
inline constexpr bool always_false_val_v = always_false_val<V>::value;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<template<typename> typename TFilter, typename... TArgs>
|
||||||
|
struct TypeFilter;
|
||||||
|
|
||||||
|
template<template<typename> typename TFilter>
|
||||||
|
struct TypeFilter<TFilter>
|
||||||
|
{
|
||||||
|
using type_t = std::tuple<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<template<typename> typename TFilter, typename TFirst, typename... TArgs>
|
||||||
|
struct TypeFilter<TFilter, TFirst, TArgs...>
|
||||||
|
{
|
||||||
|
static consteval auto makeTypeHelper()
|
||||||
|
{
|
||||||
|
using base_t = typename TypeFilter<TFilter, TArgs...>::type_t;
|
||||||
|
|
||||||
|
// note: not using decltype, as the compiler might think it is being evaluated
|
||||||
|
if constexpr (!TFilter<TFirst>::value) {
|
||||||
|
return static_cast<base_t*>(nullptr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto wrapper = []<typename... TArgsInner>(std::tuple<TArgsInner...>*)
|
||||||
|
{
|
||||||
|
return static_cast<std::tuple<TFirst, TArgsInner...>*>(nullptr);
|
||||||
|
};
|
||||||
|
return wrapper(static_cast<base_t*>(nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using type_t = std::remove_pointer_t<decltype(makeTypeHelper())>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<template<typename> typename TFilter, typename... TArgs>
|
||||||
|
auto typeFilterHelper(std::tuple<TArgs...>*)
|
||||||
|
{
|
||||||
|
return static_cast<typename TypeFilter<TFilter, TArgs...>::type_t*>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<template<typename> typename TFilter, typename TTuple>
|
||||||
|
using filter_types_t = std::remove_pointer_t<decltype(typeFilterHelper<TFilter>(static_cast<TTuple*>(nullptr)))>;
|
||||||
|
|
||||||
|
template<template<typename> typename TPredicate, typename... TArgs>
|
||||||
|
auto mapTypesHelper(std::tuple<TArgs...>)
|
||||||
|
{
|
||||||
|
return static_cast<std::tuple<TPredicate<TArgs>...>*>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<template<typename> typename TPredicate, typename TTuple>
|
||||||
|
using map_types_t = std::remove_pointer_t<decltype(mapTypesHelper<TPredicate>(std::declval<TTuple>()))>;
|
||||||
|
|
||||||
|
template<template<typename> typename TPredicate, template<typename> typename TTemplate>
|
||||||
|
struct map_template {
|
||||||
|
template<typename T>
|
||||||
|
using type_t = TTemplate<TPredicate<T>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename... Types>
|
||||||
|
struct is_any_type : std::disjunction<std::is_same<T, Types>...> {};
|
||||||
|
|
||||||
|
template<typename T, typename... Types>
|
||||||
|
static constexpr bool is_any_type_v = is_any_type<T, Types...>::value;
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename THelper, typename TType>
|
||||||
|
decltype(auto) delayEvaluation(TType&& value)
|
||||||
|
{
|
||||||
|
return static_cast<TType&&>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_TRAITS_HPP_INCLUDED)
|
127
source/mijin/util/types.hpp
Normal file
127
source/mijin/util/types.hpp
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_UTIL_TYPES_HPP_INCLUDED)
|
||||||
|
#define MIJIN_UTIL_TYPES_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include "./traits.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
template<std::size_t numBits>
|
||||||
|
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<numBits>, "No uint type with this bit count.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
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<numBits>, "No uint type with this bit count.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
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<numBits>, "No int type with this bit count.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
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<numBits>, "No int type with this bit count.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
using uintx_t = decltype(impl::getUintType<numBits>());
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
using uint_next_t = decltype(impl::getNextUintType<numBits>());
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
using intx_t = decltype(impl::getIntType<numBits>());
|
||||||
|
|
||||||
|
template<std::size_t numBits>
|
||||||
|
using int_next_t = decltype(impl::getNextIntType<numBits>());
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_UTIL_TYPES_HPP_INCLUDED)
|
128
source/mijin/virtual_filesystem/filesystem.cpp
Normal file
128
source/mijin/virtual_filesystem/filesystem.cpp
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
#include "./filesystem.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal types
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal variables
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal functions
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
std::vector<fs::path> OSFileSystemAdapter::getRoots()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"/" // TODO: other OSs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path OSFileSystemAdapter::getHomeFolder()
|
||||||
|
{
|
||||||
|
return "/home/mewin"; // very TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FileInfo> OSFileSystemAdapter::listFiles(const fs::path& folder)
|
||||||
|
{
|
||||||
|
std::vector<FileInfo> 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<Stream>& outStream)
|
||||||
|
{
|
||||||
|
const std::string pathStr = path.string();
|
||||||
|
auto stream = std::make_unique<FileStream>();
|
||||||
|
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
|
105
source/mijin/virtual_filesystem/filesystem.hpp
Normal file
105
source/mijin/virtual_filesystem/filesystem.hpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED)
|
||||||
|
#define MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#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<fs::path> getRoots() = 0;
|
||||||
|
[[nodiscard]] virtual fs::path getHomeFolder() = 0;
|
||||||
|
[[nodiscard]] virtual std::vector<FileInfo> 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<Stream>& outStream) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OSFileSystemAdapter : public FileSystemAdapter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<fs::path> getRoots() override;
|
||||||
|
fs::path getHomeFolder() override;
|
||||||
|
std::vector<FileInfo> listFiles(const fs::path& folder) override;
|
||||||
|
FileInfo getFileInfo(const fs::path& file) override;
|
||||||
|
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& 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::size_t>(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<float>(sizeInBytes) / std::pow(2.f, 10.f * static_cast<float>(pos));
|
||||||
|
oss << converted << " " << suffixes[pos];
|
||||||
|
}
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_FILESYSTEM_HPP_INCLUDED)
|
87
source/mijin/virtual_filesystem/relative.hpp
Normal file
87
source/mijin/virtual_filesystem/relative.hpp
Normal file
@ -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<typename TWrapped>
|
||||||
|
class RelativeFileSystemAdapter : public FileSystemAdapter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
TWrapped wrapped_;
|
||||||
|
fs::path root_;
|
||||||
|
public:
|
||||||
|
template<typename... TArgs>
|
||||||
|
explicit RelativeFileSystemAdapter(fs::path root, TArgs&&... args)
|
||||||
|
: wrapped_(std::forward<TArgs>(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<fs::path> getRoots() override;
|
||||||
|
fs::path getHomeFolder() override;
|
||||||
|
std::vector<FileInfo> listFiles(const fs::path& folder) override;
|
||||||
|
FileInfo getFileInfo(const fs::path& file) override;
|
||||||
|
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TWrapped>
|
||||||
|
std::vector<fs::path> RelativeFileSystemAdapter<TWrapped>::getRoots()
|
||||||
|
{
|
||||||
|
return { root_ };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TWrapped>
|
||||||
|
fs::path RelativeFileSystemAdapter<TWrapped>::getHomeFolder()
|
||||||
|
{
|
||||||
|
return root_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TWrapped>
|
||||||
|
std::vector<FileInfo> RelativeFileSystemAdapter<TWrapped>::listFiles(const fs::path& folder)
|
||||||
|
{
|
||||||
|
std::vector<FileInfo> result = wrapped_.listFiles(root_ / folder);
|
||||||
|
for (FileInfo& fileInfo : result) {
|
||||||
|
fileInfo.path = "/" / fileInfo.path.lexically_relative(root_);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TWrapped>
|
||||||
|
FileInfo RelativeFileSystemAdapter<TWrapped>::getFileInfo(const fs::path& file)
|
||||||
|
{
|
||||||
|
return wrapped_.getFileInfo(root_ / file);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TWrapped>
|
||||||
|
StreamError RelativeFileSystemAdapter<TWrapped>::open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream)
|
||||||
|
{
|
||||||
|
return wrapped_.open(root_ / path, mode, outStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_RELATIVE_HPP_INCLUDED)
|
105
source/mijin/virtual_filesystem/stacked.cpp
Normal file
105
source/mijin/virtual_filesystem/stacked.cpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
|
||||||
|
#include "./stacked.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal types
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal variables
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal functions
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
std::vector<fs::path> StackedFileSystemAdapter::getRoots()
|
||||||
|
{
|
||||||
|
std::vector<fs::path> 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<FileInfo> StackedFileSystemAdapter::listFiles(const fs::path& folder)
|
||||||
|
{
|
||||||
|
std::vector<FileInfo> 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<Stream>& 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
|
52
source/mijin/virtual_filesystem/stacked.hpp
Normal file
52
source/mijin/virtual_filesystem/stacked.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED)
|
||||||
|
#define MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include "./filesystem.hpp"
|
||||||
|
|
||||||
|
namespace mijin
|
||||||
|
{
|
||||||
|
|
||||||
|
//
|
||||||
|
// public defines
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public constants
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public types
|
||||||
|
//
|
||||||
|
|
||||||
|
class StackedFileSystemAdapter : public FileSystemAdapter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<FileSystemAdapter>> adapters_;
|
||||||
|
public:
|
||||||
|
std::vector<fs::path> getRoots() override;
|
||||||
|
fs::path getHomeFolder() override;
|
||||||
|
std::vector<FileInfo> listFiles(const fs::path& folder) override;
|
||||||
|
FileInfo getFileInfo(const fs::path& file) override;
|
||||||
|
StreamError open(const fs::path& path, FileOpenMode mode, std::unique_ptr<Stream>& outStream) override;
|
||||||
|
|
||||||
|
inline void addAdapter(std::unique_ptr<FileSystemAdapter>&& adapter) {
|
||||||
|
adapters_.push_back(std::move(adapter));
|
||||||
|
}
|
||||||
|
template<typename TAdapter, typename... TArgs>
|
||||||
|
inline void emplaceAdapter(TArgs&&... args) {
|
||||||
|
addAdapter(std::make_unique<TAdapter>(std::forward<TArgs>(args)...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// public functions
|
||||||
|
//
|
||||||
|
|
||||||
|
} // namespace mijin
|
||||||
|
|
||||||
|
#endif // !defined(MIJIN_VIRTUAL_FILESYSTEM_STACKED_HPP_INCLUDED)
|
Loading…
x
Reference in New Issue
Block a user